From 2429159020bd33aba207ecbc1b514c9f2c07ed14 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 20 Oct 2025 23:04:04 +0700 Subject: [PATCH 1/5] fix: send token in keycloak function & change script fix retire --- src/controllers/OrganizationController.ts | 620 ++++++++++++++-------- src/keycloak/index.ts | 29 +- 2 files changed, 429 insertions(+), 220 deletions(-) diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 64ae57b1..4cb17e6d 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -1,3 +1,4 @@ +import { RoleKeycloak } from "./../entities/RoleKeycloak"; import { ProfileEmployee } from "./../entities/ProfileEmployee"; import { EmployeePosition } from "./../entities/EmployeePosition"; import { EmployeePosMaster } from "./../entities/EmployeePosMaster"; @@ -37,7 +38,14 @@ import { sendToQueueOrg, sendToQueueOrgDraft } from "../services/rabbitmq"; import { PosType } from "../entities/PosType"; import { PosLevel } from "../entities/PosLevel"; import { PermissionOrg } from "../entities/PermissionOrg"; -import { deleteUser, getToken } from "../keycloak"; +import { + deleteUser, + getToken, + createUser, + getUserByUsername, + getRoles, + addUserRoles, +} from "../keycloak"; import { CreatePosMasterHistoryEmployee, CreatePosMasterHistoryOfficer, @@ -69,6 +77,8 @@ export class OrganizationController extends Controller { private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); private employeePosMasterRepository = AppDataSource.getRepository(EmployeePosMaster); private employeePositionRepository = AppDataSource.getRepository(EmployeePosition); + private roleKeycloakRepo = AppDataSource.getRepository(RoleKeycloak); + /** * API ล้างข้อมูล * @@ -1973,7 +1983,7 @@ export class OrganizationController extends Controller { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูล"); } let _privilege = await new permission().PermissionOrgList(request, "SYS_ORG"); - + const attrOwnership = _privilege.root === null ? true : false; const profile = await this.profileRepo.findOne({ @@ -1988,26 +1998,26 @@ export class OrganizationController extends Controller { ?.posMasterAssigns.find((x) => x.assignId === "SYS_ORG"); if (orgRevision.orgRevisionIsDraft && !orgRevision.orgRevisionIsCurrent && !attrOwnership) { - if(Array.isArray(profile.permissionProfiles) && profile.permissionProfiles.length > 0){ + if (Array.isArray(profile.permissionProfiles) && profile.permissionProfiles.length > 0) { _data.root = profile.permissionProfiles.map((x) => x.orgRootId); - }else{ + } else { return new HttpSuccess({ remark: "", data: [] }); - } + } } // กำหนดการเข้าถึงข้อมูลตามสถานะและสิทธิ์ const isCurrentActive = !orgRevision.orgRevisionIsDraft && orgRevision.orgRevisionIsCurrent; if (isCurrentActive) { if (profileAssign && _privilege.privilege !== "OWNER") { - if(_privilege.privilege == "NORMAL"){ - const holder = profile.current_holders.find(x => x.orgRevisionId === id); + if (_privilege.privilege == "NORMAL") { + const holder = profile.current_holders.find((x) => x.orgRevisionId === id); if (!holder) return; _data.root = [holder.orgRootId]; _data.child1 = [holder.orgChild1Id]; _data.child2 = [holder.orgChild2Id]; _data.child3 = [holder.orgChild3Id]; _data.child4 = [holder.orgChild4Id]; - }else if(_privilege.privilege == "CHILD"){ - const holder = profile.current_holders.find(x => x.orgRevisionId === id); + } else if (_privilege.privilege == "CHILD") { + const holder = profile.current_holders.find((x) => x.orgRevisionId === id); if (!holder) return; _data.root = [holder.orgRootId]; if (_privilege.root && _privilege.child1 === null) { @@ -2022,7 +2032,7 @@ export class OrganizationController extends Controller { _data.child3 = [holder.orgChild3Id]; _data.child4 = [holder.orgChild4Id]; } - }else{ + } else { _data.root = [profile.current_holders.find((x) => x.orgRevisionId === id)?.orgRootId]; } } else { @@ -7318,54 +7328,70 @@ export class OrganizationController extends Controller { */ @Get("delete/profile/org/{orgRevisionId}") async deleteRetireInOrg(@Path() orgRevisionId: string, @Request() request: RequestWithUser) { - // const posMasters = await this.posMasterRepository.find({ - // where: { - // orgRevisionId: orgRevisionId, - // current_holder: { - // isLeave: true, - // }, - // }, - // }); - - // let check = 0; - // await Promise.all( - // posMasters.map(async (posMaster) => { - // posMaster.current_holderId = null; - // await this.posMasterRepository.save(posMaster); - // check += 1; - // }), - // ); - - const posMastersEmployee = await this.employeePosMasterRepository.find({ - where: { - orgRevisionId: orgRevisionId, - current_holder: { - isLeave: true, - leaveType: "RETIRE", + const [posMasters, posMastersEmployee] = await Promise.all([ + this.posMasterRepository.find({ + where: { + orgRevisionId, + current_holder: { + isLeave: true, + isRetirement: true, + leaveType: IsNull(), + }, + positions: { positionIsSelected: true }, }, - positions: { positionIsSelected: true }, - }, - }); + relations: ["positions"], + }), + this.employeePosMasterRepository.find({ + where: { + orgRevisionId, + current_holder: { + isLeave: true, + isRetirement: true, + leaveType: IsNull(), + }, + }, + relations: ["positions"], + }), + ]); - let check = 0; - await Promise.all( + let checkOfficer = 0; + let checkEmployee = 0; + + await Promise.all([ + posMasters.map(async (posMaster) => { + posMaster.current_holderId = null; + posMaster.isSit = false; + if (posMaster.positions) { + for (const position of posMaster.positions) { + position.positionIsSelected = false; + await this.positionRepository.save(position); + checkOfficer += 1; + } + } + await this.posMasterRepository.save(posMaster); + await CreatePosMasterHistoryOfficer(posMaster.id, null); + }), posMastersEmployee.map(async (posMaster) => { posMaster.current_holderId = null; + posMaster.isSit = false; if (posMaster.positions) { for (const position of posMaster.positions) { position.positionIsSelected = false; await this.employeePositionRepository.save(position); - check += 1; + checkEmployee += 1; } } await this.employeePosMasterRepository.save(posMaster); await CreatePosMasterHistoryEmployee(posMaster.id, null); - check += 1; }), - ); - // จำนวนคนที่ถูกลบออกจากโครงสร้าง - const total = posMastersEmployee.length; - return new HttpSuccess({ total, successAmount: check }); + ]); + + return new HttpSuccess({ + totalOfficer: posMasters.length, + totalEmployee: posMastersEmployee.length, + officerSuccessAmount: checkOfficer, + employeeSuccessAmount: checkEmployee, + }); } /** @@ -7376,75 +7402,78 @@ export class OrganizationController extends Controller { */ @Get("save/profile/position-history") async saveRetireToPositionHistory(@Request() request: RequestWithUser) { - // const profileLeave = await this.profileRepo.find({ - // where: { - // isLeave: true, - // leaveType: "RETIRE", - // }, - // }); + const [profileLeave, profileEmployeeLeave] = await Promise.all([ + this.profileRepo.find({ + where: { + isLeave: true, + isRetirement: true, + leaveType: IsNull(), + }, + }), + this.profileEmployeeRepo.find({ + where: { + isLeave: true, + isRetirement: true, + leaveType: IsNull(), + }, + }), + ]); - // let check: number = 0; - // await Promise.all( - // profileLeave.map(async (profile: any) => { - // const dest_item = await this.profileSalaryRepository.findOne({ - // where: { profileId: profile.id }, - // order: { order: "DESC" }, - // }); - // const data: any = { - // order: dest_item == null ? 1 : dest_item.order + 1, - // amount: null, - // positionSalaryAmount: null, - // mouthSalaryAmount: null, - // profileId: profile.id, - // posNo: null, - // positionExecutive: null, - // positionType: null, - // positionLevel: null, - // amountSpecial: null, - // orgRoot: null, - // orgChild1: null, - // orgChild2: null, - // orgChild3: null, - // orgChild4: null, - // commandYear: new Date().getFullYear() + 543, - // commandDateAffect: profile.dateLeave, - // commandCode: "16", - // commandName: "พ้นจากราชการ", - // posNoAbb: null, - // isEntry: false, - // positionName: "เกษียณอายุราชการ", - // createdUserId: request.user.sub, - // createdFullName: request.user.name, - // lastUpdateUserId: request.user.sub, - // lastUpdateFullName: request.user.name, - // createdAt: new Date(), - // lastUpdatedAt: new Date(), - // remark: "ประกาศคณะอนุกรรมการสามัญข้าราชการกรุงเทพมหานครสามัญ ลว. 31 มี.ค. 68", // script เกษียณจริง ๆ ให้เอา “วันที่ประกาศเกษียณฉบับแรก” มาลงในเอกสารอ้างอิง - // isGovernment: false, - // }; - - // const history = new ProfileSalaryHistory(); - // Object.assign(history, { ...data, id: undefined }); - // data.dateGovernment = profile.dateLeave; - // const savedData = await this.profileSalaryRepository.save(data); - - // history.profileSalaryId = savedData.id; - // await this.salaryHistoryRepo.save(history); - - // check += 1; - // }), - // ); - - const profileLeave = await this.profileEmployeeRepo.find({ - where: { - isLeave: true, - leaveType: "RETIRE", - }, - }); - - let check: number = 0; + let checkOfficer: number = 0; await Promise.all( profileLeave.map(async (profile: any) => { + const dest_item = await this.profileSalaryRepository.findOne({ + where: { profileId: profile.id }, + order: { order: "DESC" }, + }); + const data: any = { + order: dest_item == null ? 1 : dest_item.order + 1, + amount: null, + positionSalaryAmount: null, + mouthSalaryAmount: null, + profileId: profile.id, + posNo: null, + positionExecutive: null, + positionType: null, + positionLevel: null, + amountSpecial: null, + orgRoot: null, + orgChild1: null, + orgChild2: null, + orgChild3: null, + orgChild4: null, + commandYear: new Date().getFullYear() + 543, + commandDateAffect: profile.dateLeave, + commandCode: "16", + commandName: "พ้นจากราชการ", + posNoAbb: null, + isEntry: false, + positionName: "เกษียณอายุราชการ", + createdUserId: request.user.sub, + createdFullName: request.user.name, + lastUpdateUserId: request.user.sub, + lastUpdateFullName: request.user.name, + createdAt: new Date(), + lastUpdatedAt: new Date(), + remark: "ประกาศคณะอนุกรรมการสามัญข้าราชการกรุงเทพมหานครสามัญ ลว. 31 มี.ค. 68", // script เกษียณจริง ๆ ให้เอา “วันที่ประกาศเกษียณฉบับแรก” มาลงในเอกสารอ้างอิง + isGovernment: false, + }; + + const history = new ProfileSalaryHistory(); + Object.assign(history, { ...data, id: undefined }); + data.dateGovernment = profile.dateLeave; + const savedData = await this.profileSalaryRepository.save(data); + + history.profileSalaryId = savedData.id; + await this.salaryHistoryRepo.save(history); + + checkOfficer += 1; + }), + ); + + let checkEmployee: number = 0; + await Promise.all( + profileEmployeeLeave.map(async (profile: any) => { const dest_item = await this.profileSalaryRepository.findOne({ where: { profileEmployeeId: profile.id }, order: { order: "DESC" }, @@ -7490,13 +7519,20 @@ export class OrganizationController extends Controller { history.profileSalaryId = savedData.id; await this.salaryHistoryRepo.save(history); - check += 1; + checkEmployee += 1; }), ); + // จำนวนคนที่บันทึกลงประวัติตำแหน่ง - const total = profileLeave.length; + const totalOfficer = profileLeave.length; + const totalEmployee = profileEmployeeLeave.length; // จำนวนคนที่ถูกบันทึกลงประวัติตำแหน่งสำเร็จ - return new HttpSuccess({ total, successAmount: check }); + return new HttpSuccess({ + totalOfficer, + totalEmployee, + officerSuccessAmount: checkOfficer, + successEmployeeAmount: checkEmployee, + }); } /** @@ -7507,41 +7543,19 @@ export class OrganizationController extends Controller { */ @Get("update/profile/leave-reason") async updateRetireReason() { - // const profileLeave = await this.profileRepo.find({ - // where: { - // isLeave: true, - // leaveType: "RETIRE", - // }, - // }); - - // let check: number = 0; - // let notDelete: string[] = []; - // await Promise.all( - // profileLeave.map(async (profile) => { - // profile.leaveReason = "เกษียณอายุราชการ"; - // profile.isActive = false; - - // if (profile.keycloak != null && profile.keycloak != "") { - // const delUserKeycloak = await deleteUser(profile.keycloak); - // if (delUserKeycloak) { - // profile.keycloak = ""; - // profile.roleKeycloaks = []; - // check += 1; - // } else { - // // push array not delete - // notDelete.push(profile.keycloak); - // } - // } - - // await this.profileRepo.save(profile); - // }), - // ); - - const [profileLeave, token] = await Promise.all([ + const [profileLeave, profileEmployeeLeave, token] = await Promise.all([ + this.profileRepo.find({ + where: { + isLeave: true, + isRetirement: true, + leaveType: IsNull(), + }, + }), this.profileEmployeeRepo.find({ where: { isLeave: true, - leaveType: "RETIRE", + isRetirement: true, + leaveType: IsNull(), }, }), getToken(), @@ -7550,77 +7564,263 @@ export class OrganizationController extends Controller { if (!token) throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถเชื่อมต่อ Keycloak"); - let check: number = 0; - let notDelete: string[] = []; + let checkOfficer: number = 0; + let notDeleteOfficer: string[] = []; - // loop batch at 50 profiles - const chunkSize = 50; - for (let i = 0; i < profileLeave.length; i += chunkSize) { - const chunk = profileLeave.slice(i, i + chunkSize); - await Promise.all( - chunk.map(async (profile) => { - profile.leaveReason = "เกษียณอายุราชการ"; - profile.isActive = false; - - if (profile.keycloak != null && profile.keycloak != "") { - const delUserKeycloak = await deleteUser(profile.keycloak, token); - if (delUserKeycloak) { - profile.keycloak = ""; - profile.roleKeycloaks = []; - check += 1; - } else { - // push array not delete - notDelete.push(profile.keycloak); - } - } - - await this.profileEmployeeRepo.save(profile); - }), - ); - } - - // จำนวนคนที่ถูกแก้ไขเหตุผลการลาออก - const total = profileLeave.length; - return new HttpSuccess({ total, successAmount: check, notDelete }); - } - - /** - * API ลบตำแหน่งที่ครองอยู่ของข้าราชการที่รันเกษียณไปแล้ว - * - * @summary - ลบตำแหน่งที่ครองอยู่ของข้าราชการที่รันเกษียณไปแล้ว (ADMIN) - * - */ - @Get("update/org/position/remove-select/{orgRevisionId}") - async updatePositionSelectOrg( - @Path() orgRevisionId: string, - @Request() request: RequestWithUser, - ) { - const posMasters = await this.posMasterRepository.find({ - where: { - orgRevisionId: orgRevisionId, - current_holderId: IsNull(), - positions: { positionIsSelected: true }, - }, - relations: ["positions"], - }); - - // update position positionIsSelected = 0 - let check = 0; await Promise.all( - posMasters.map(async (posMaster) => { - if (posMaster.positions && posMaster.positions.length > 0) { - for (const position of posMaster.positions) { - position.positionIsSelected = false; - await this.positionRepository.save(position); - check += 1; + profileLeave.map(async (profile) => { + profile.leaveReason = "เกษียณอายุราชการ"; + profile.leaveType = "RETIRE"; + profile.isActive = false; + + if (profile.keycloak != null && profile.keycloak != "") { + const delUserKeycloak = await deleteUser(profile.keycloak, token); + if (delUserKeycloak) { + profile.keycloak = ""; + profile.roleKeycloaks = []; + checkOfficer += 1; + } else { + // push array not delete + notDeleteOfficer.push(profile.keycloak); } - await CreatePosMasterHistoryOfficer(posMaster.id, null); } + + await this.profileRepo.save(profile); }), ); - // จำนวนคนที่ถูกลบออกจากโครงสร้าง - const total = posMasters.length; + let checkEmployee: number = 0; + let notDeleteEmployee: string[] = []; + + await Promise.all( + profileEmployeeLeave.map(async (profileEmp) => { + profileEmp.leaveReason = "เกษียณอายุราชการ"; + profileEmp.leaveType = "RETIRE"; + profileEmp.leaveDate = new Date("2025-09-30"); + profileEmp.dateLeave = new Date("2025-09-30"); + profileEmp.lastUpdatedAt = new Date(); + profileEmp.isActive = false; + + if (profileEmp.keycloak != null && profileEmp.keycloak != "") { + const delUserKeycloak = await deleteUser(profileEmp.keycloak, token); + if (delUserKeycloak) { + profileEmp.keycloak = ""; + profileEmp.roleKeycloaks = []; + checkEmployee += 1; + } else { + // push array not delete + notDeleteEmployee.push(profileEmp.keycloak); + } + } + + await this.profileEmployeeRepo.save(profileEmp); + }), + ); + + // loop batch at 50 profiles + // const chunkSize = 50; + // for (let i = 0; i < profileLeave.length; i += chunkSize) { + // const chunk = profileLeave.slice(i, i + chunkSize); + // await Promise.all( + // chunk.map(async (profile) => { + // profile.leaveReason = "เกษียณอายุราชการ"; + // profile.isActive = false; + + // if (profile.keycloak != null && profile.keycloak != "") { + // const delUserKeycloak = await deleteUser(profile.keycloak, token); + // if (delUserKeycloak) { + // profile.keycloak = ""; + // profile.roleKeycloaks = []; + // check += 1; + // } else { + // // push array not delete + // notDelete.push(profile.keycloak); + // } + // } + + // await this.profileEmployeeRepo.save(profile); + // }), + // ); + // } + + // จำนวนคนที่ถูกแก้ไขเหตุผลการลาออก + const total = profileLeave.length; + return new HttpSuccess({ + total, + successOfficerAmount: checkOfficer, + notDeleteOfficer, + successEmployeeAmount: checkEmployee, + notDeleteEmployee, + }); + } + + /** + * API สร้าง keycloak ใหม่สำหรับข้าราชการที่รันเกษียณผิด + * + * @summary - สร้าง keycloak ใหม่สำหรับข้าราชการที่รันเกษียณผิด (ADMIN) + * + */ + @Get("update/org/profile-officer/create-keycloak") + async createKeycloakForOfficer(@Request() request: RequestWithUser) { + const [profiles, token] = await Promise.all([ + this.profileRepo.find({ + where: { + keycloak: IsNull(), + isActive: true, + }, + relations: ["roleKeycloaks"], + }), + getToken(), + ]); + + if (!token) + throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถเชื่อมต่อ Keycloak"); + + let check: number = 0; + for await (const _item of profiles) { + let password = _item.citizenId; + if (_item.birthDate != null) { + const _date = new Date(_item.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(_item.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(_item.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + const checkUser = await getUserByUsername(_item.citizenId, token); + let userId: any = ""; + if (checkUser.length == 0) { + userId = await createUser( + _item.citizenId, + password, + { + firstName: _item.firstName, + lastName: _item.lastName, + // email: _item.email, + }, + token, + ); + if (typeof userId !== "string") { + throw new Error(userId.errorMessage); + } + } else { + userId = checkUser[0].id; + } + + const list = await getRoles("", token); + if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); + const result = await addUserRoles( + userId, + list.filter((v) => v.id == "8a1a0dc9-304c-4e5b-a90a-65f841048212"), + token, + ); + + if (!result) { + throw new Error("Failed. Cannot set user's role."); + } + if (typeof userId === "string") { + _item.keycloak = userId; + } + const roleKeycloak = await this.roleKeycloakRepo.find({ + where: { id: "8a1a0dc9-304c-4e5b-a90a-65f841048212" }, + }); + if (_item) { + _item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak])); + this.profileRepo.save(_item); + check += 1; + } + } + + const total = profiles.length; + + return new HttpSuccess({ total, successAmount: check }); + } + + /** + * API สร้าง keycloak ใหม่สำหรับลูกจ้างที่รันเกษียณผิด + * + * @summary - สร้าง keycloak ใหม่สำหรับลูกจ้างที่รันเกษียณผิด (ADMIN) + * + */ + @Get("update/org/profile-employee/create-keycloak") + async createKeycloakForEmployee(@Request() request: RequestWithUser) { + const [profiles, token] = await Promise.all([ + this.profileEmployeeRepo.find({ + where: { + keycloak: IsNull(), + isActive: true, + }, + relations: ["roleKeycloaks"], + }), + getToken(), + ]); + + if (!token) + throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถเชื่อมต่อ Keycloak"); + + let check: number = 0; + for await (const _item of profiles) { + let password = _item.citizenId; + if (_item.birthDate != null) { + const _date = new Date(_item.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(_item.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(_item.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + const checkUser = await getUserByUsername(_item.citizenId, token); + let userId: any = ""; + if (checkUser.length == 0) { + userId = await createUser( + _item.citizenId, + password, + { + firstName: _item.firstName, + lastName: _item.lastName, + // email: _item.email, + }, + token, + ); + if (typeof userId !== "string") { + throw new Error(userId.errorMessage); + } + } else { + userId = checkUser[0].id; + } + + const list = await getRoles("", token); + if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); + const result = await addUserRoles( + userId, + list.filter((v) => v.id == "8a1a0dc9-304c-4e5b-a90a-65f841048212"), + token, + ); + + if (!result) { + throw new Error("Failed. Cannot set user's role."); + } + if (typeof userId === "string") { + _item.keycloak = userId; + } + const roleKeycloak = await this.roleKeycloakRepo.find({ + where: { id: "8a1a0dc9-304c-4e5b-a90a-65f841048212" }, + }); + if (_item) { + _item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak])); + this.profileEmployeeRepo.save(_item); + check += 1; + } + } + + const total = profiles.length; + return new HttpSuccess({ total, successAmount: check }); } } diff --git a/src/keycloak/index.ts b/src/keycloak/index.ts index c14a6b62..a81e2af9 100644 --- a/src/keycloak/index.ts +++ b/src/keycloak/index.ts @@ -67,11 +67,16 @@ export async function getToken() { * * @returns user uuid or true if success, false otherwise. */ -export async function createUser(username: string, password: string, opts?: Record) { +export async function createUser( + username: string, + password: string, + opts?: Record, + token?: string, +) { const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, { // prettier-ignore headers: { - "authorization": `Bearer ${await getToken()}`, + "authorization": `Bearer ${token || await getToken()}`, "content-type": `application/json`, }, method: "POST", @@ -101,11 +106,11 @@ export async function createUser(username: string, password: string, opts?: Reco * * @returns user if success, false otherwise. */ -export async function getUser(userId: string) { +export async function getUser(userId: string, token?: string) { const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, { // prettier-ignore headers: { - "authorization": `Bearer ${await getToken()}`, + "authorization": `Bearer ${token || await getToken()}`, "content-type": `application/json`, }, }).catch((e) => console.log("Keycloak Error: ", e)); @@ -123,11 +128,11 @@ export async function getUser(userId: string) { * * @returns user if success, false otherwise. */ -export async function getUserByUsername(citizenId: string) { +export async function getUserByUsername(citizenId: string, token?: string) { const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users?username=${citizenId}`, { // prettier-ignore headers: { - "authorization": `Bearer ${await getToken()}`, + "authorization": `Bearer ${token || await getToken()}`, "content-type": `application/json`, }, }).catch((e) => console.log("Keycloak Error: ", e)); @@ -409,13 +414,13 @@ export async function getRoleMappings(userId: string) { * * @returns role's info (array if not specify name) if success, null if not found, false otherwise. */ -export async function getRoles(name?: string) { +export async function getRoles(name?: string, token?: string) { const res = await fetch( `${KC_URL}/admin/realms/${KC_REALMS}/roles`.concat((name && `/${name}`) || ""), { // prettier-ignore headers: { - "authorization": `Bearer ${await getToken()}`, + "authorization": `Bearer ${token || await getToken()}`, }, }, ).catch((e) => console.log(e)); @@ -491,13 +496,17 @@ export async function getUserRoles(userId: string) { * * @returns true if success, false otherwise. */ -export async function addUserRoles(userId: string, roles: { id: string; name: string }[]) { +export async function addUserRoles( + userId: string, + roles: { id: string; name: string }[], + token?: string, +) { const res = await fetch( `${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/role-mappings/realm`, { // prettier-ignore headers: { - "authorization": `Bearer ${await getToken()}`, + "authorization": `Bearer ${token || await getToken()}`, "content-type": `application/json`, }, method: "POST", From e59ccf88b366a8e2e2bff8fab838a23765432321 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 21 Oct 2025 00:11:18 +0700 Subject: [PATCH 2/5] fix: script create keycloak --- src/controllers/OrganizationController.ts | 145 ++++++++++++---------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 4cb17e6d..47e97934 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -7747,80 +7747,89 @@ export class OrganizationController extends Controller { */ @Get("update/org/profile-employee/create-keycloak") async createKeycloakForEmployee(@Request() request: RequestWithUser) { - const [profiles, token] = await Promise.all([ - this.profileEmployeeRepo.find({ - where: { - keycloak: IsNull(), - isActive: true, - }, - relations: ["roleKeycloaks"], - }), - getToken(), - ]); - - if (!token) - throw new HttpError(HttpStatusCode.INTERNAL_SERVER_ERROR, "ไม่สามารถเชื่อมต่อ Keycloak"); - let check: number = 0; - for await (const _item of profiles) { - let password = _item.citizenId; - if (_item.birthDate != null) { - const _date = new Date(_item.birthDate.toDateString()) - .getDate() - .toString() - .padStart(2, "0"); - const _month = (new Date(_item.birthDate.toDateString()).getMonth() + 1) - .toString() - .padStart(2, "0"); - const _year = new Date(_item.birthDate.toDateString()).getFullYear() + 543; - password = `${_date}${_month}${_year}`; - } - const checkUser = await getUserByUsername(_item.citizenId, token); - let userId: any = ""; - if (checkUser.length == 0) { - userId = await createUser( - _item.citizenId, - password, - { - firstName: _item.firstName, - lastName: _item.lastName, - // email: _item.email, - }, - token, - ); - if (typeof userId !== "string") { - throw new Error(userId.errorMessage); - } - } else { - userId = checkUser[0].id; - } + const profiles = await this.profileEmployeeRepo.find({ + where: { + keycloak: IsNull(), + isLeave: false, + isRetirement: false, + }, + order: { citizenId: "ASC" }, + relations: ["roleKeycloaks"], + }); - const list = await getRoles("", token); - if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); - const result = await addUserRoles( - userId, - list.filter((v) => v.id == "8a1a0dc9-304c-4e5b-a90a-65f841048212"), - token, - ); + // ดึงข้อมูลที่ใช้บ่อยก่อน (cache) + const rolesList = await getRoles(); + if (!Array.isArray(rolesList)) + throw new Error("Failed. Cannot get role(s) data from the server."); - if (!result) { - throw new Error("Failed. Cannot set user's role."); - } - if (typeof userId === "string") { - _item.keycloak = userId; - } - const roleKeycloak = await this.roleKeycloakRepo.find({ - where: { id: "8a1a0dc9-304c-4e5b-a90a-65f841048212" }, - }); - if (_item) { - _item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak])); - this.profileEmployeeRepo.save(_item); - check += 1; - } + const roleKeycloak = await this.roleKeycloakRepo.find({ + where: { id: "8a1a0dc9-304c-4e5b-a90a-65f841048212" }, + }); + + // Process แบบ batch เพื่อลดการเรียก API ทีละตัว + const batchSize = 100; + const batches = []; + for (let i = 0; i < profiles.length; i += batchSize) { + batches.push(profiles.slice(i, i + batchSize)); } + for (const batch of batches) { + await Promise.all( + batch.map(async (_item) => { + let password = _item.citizenId; + if (_item.birthDate != null) { + const _date = new Date(_item.birthDate.toDateString()) + .getDate() + .toString() + .padStart(2, "0"); + const _month = (new Date(_item.birthDate.toDateString()).getMonth() + 1) + .toString() + .padStart(2, "0"); + const _year = new Date(_item.birthDate.toDateString()).getFullYear() + 543; + password = `${_date}${_month}${_year}`; + } + try { + const checkUser = await getUserByUsername(_item.citizenId); + let userId: any = ""; + if (checkUser.length == 0) { + userId = await createUser(_item.citizenId, password, { + firstName: _item.firstName, + lastName: _item.lastName, + }); + if (typeof userId !== "string") { + console.error(`Failed to create user for ${_item.citizenId}:`, userId.errorMessage); + return; + } + } else { + userId = checkUser[0].id; + } + + const result = await addUserRoles( + userId, + rolesList.filter((v) => v.id == "8a1a0dc9-304c-4e5b-a90a-65f841048212"), + ); + + if (!result) { + console.error(`Failed to set role for user ${_item.citizenId}`); + return; + } + + if (typeof userId === "string") { + _item.keycloak = userId; + } + if (_item) { + _item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak])); + check += 1; + await this.profileEmployeeRepo.save(_item); + } + } catch (error) { + console.error(`Error processing ${_item.citizenId}:`, error); + } + }), + ); + } const total = profiles.length; - return new HttpSuccess({ total, successAmount: check }); } } From 42c8f34f1510483d430cb29f99d8e538d8cbd7d6 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Tue, 21 Oct 2025 10:12:23 +0700 Subject: [PATCH 3/5] fix script: retire remove from org --- src/controllers/OrganizationController.ts | 83 ++++++++++++++--------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/src/controllers/OrganizationController.ts b/src/controllers/OrganizationController.ts index 47e97934..8544c1f3 100644 --- a/src/controllers/OrganizationController.ts +++ b/src/controllers/OrganizationController.ts @@ -7321,41 +7321,30 @@ export class OrganizationController extends Controller { } /** - * API ลบคนในโครงสร้าง + * API ลบข้าราชการในโครงสร้าง * - * @summary - ลบคนในโครงสร้าง (ADMIN) + * @summary - ลบข้าราชการในโครงสร้าง (ADMIN) * */ - @Get("delete/profile/org/{orgRevisionId}") - async deleteRetireInOrg(@Path() orgRevisionId: string, @Request() request: RequestWithUser) { - const [posMasters, posMastersEmployee] = await Promise.all([ - this.posMasterRepository.find({ - where: { - orgRevisionId, - current_holder: { - isLeave: true, - isRetirement: true, - leaveType: IsNull(), - }, - positions: { positionIsSelected: true }, + @Get("delete/profile-officer/org/{orgRevisionId}") + async deleteOfficerRetireInOrg( + @Path() orgRevisionId: string, + @Request() request: RequestWithUser, + ) { + const posMasters = await this.posMasterRepository.find({ + where: { + orgRevisionId, + current_holderId: Not(IsNull()), + current_holder: { + isLeave: true, + isRetirement: true, }, - relations: ["positions"], - }), - this.employeePosMasterRepository.find({ - where: { - orgRevisionId, - current_holder: { - isLeave: true, - isRetirement: true, - leaveType: IsNull(), - }, - }, - relations: ["positions"], - }), - ]); + // positions: { positionIsSelected: true }, + }, + relations: ["positions"], + }); let checkOfficer = 0; - let checkEmployee = 0; await Promise.all([ posMasters.map(async (posMaster) => { @@ -7371,6 +7360,38 @@ export class OrganizationController extends Controller { await this.posMasterRepository.save(posMaster); await CreatePosMasterHistoryOfficer(posMaster.id, null); }), + ]); + + return new HttpSuccess({ + totalOfficer: posMasters.length, + officerSuccessAmount: checkOfficer, + }); + } + + /** + * API ลบลูกจ้างในโครงสร้าง + * + * @summary - ลบลูกจ้างในโครงสร้าง (ADMIN) + * + */ + @Get("delete/profile-emp/org/{orgRevisionId}") + async deleteRetireEmpInOrg(@Path() orgRevisionId: string, @Request() request: RequestWithUser) { + const posMastersEmployee = await this.employeePosMasterRepository.find({ + where: { + orgRevisionId, + current_holderId: Not(IsNull()), + current_holder: { + isLeave: true, + isRetirement: true, + }, + // positions: { positionIsSelected: true }, + }, + relations: ["positions"], + }); + + let checkEmployee = 0; + + await Promise.all( posMastersEmployee.map(async (posMaster) => { posMaster.current_holderId = null; posMaster.isSit = false; @@ -7384,12 +7405,10 @@ export class OrganizationController extends Controller { await this.employeePosMasterRepository.save(posMaster); await CreatePosMasterHistoryEmployee(posMaster.id, null); }), - ]); + ); return new HttpSuccess({ - totalOfficer: posMasters.length, totalEmployee: posMastersEmployee.length, - officerSuccessAmount: checkOfficer, employeeSuccessAmount: checkEmployee, }); } From 9ba31d40eb9f9d44fc60bbd6a70d5fd5aa5b5277 Mon Sep 17 00:00:00 2001 From: adisak Date: Tue, 21 Oct 2025 15:27:41 +0700 Subject: [PATCH 4/5] #1888 --- src/controllers/OrganizationDotnetController.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index fb615566..67d39ed8 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -4389,7 +4389,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "ROOT") { + if (body.role === "CHILD" || body.role === "PARENT") { switch (body.node) { case 0: typeCondition = { @@ -4516,6 +4516,12 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } + } else if (body.role === "ROOT") { + typeCondition = { + orgRoot: { + ancestorDNA: body.nodeId, + }, + }; } let profile = await this.profileRepo.find({ From 783bb95e16bc0af19ae74785e818d34039d8c78d Mon Sep 17 00:00:00 2001 From: adisak Date: Tue, 21 Oct 2025 17:42:21 +0700 Subject: [PATCH 5/5] fix --- src/controllers/OrganizationDotnetController.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/controllers/OrganizationDotnetController.ts b/src/controllers/OrganizationDotnetController.ts index 67d39ed8..52acf7f0 100644 --- a/src/controllers/OrganizationDotnetController.ts +++ b/src/controllers/OrganizationDotnetController.ts @@ -4430,7 +4430,7 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "OWNER") { + } else if (body.role === "OWNER" || body.role === "ROOT") { switch (body.reqNode) { case 0: typeCondition = { @@ -4516,12 +4516,6 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "ROOT") { - typeCondition = { - orgRoot: { - ancestorDNA: body.nodeId, - }, - }; } let profile = await this.profileRepo.find({ @@ -4990,7 +4984,7 @@ export class OrganizationDotnetController extends Controller { }, ) { let typeCondition: any = {}; - if (body.role === "CHILD" || body.role === "PARENT" || body.role === "ROOT") { + if (body.role === "CHILD" || body.role === "PARENT") { switch (body.node) { case 0: typeCondition = { @@ -5034,7 +5028,7 @@ export class OrganizationDotnetController extends Controller { typeCondition = {}; break; } - } else if (body.role === "OWNER") { + } else if (body.role === "OWNER" || body.role === "ROOT") { switch (body.reqNode) { case 0: typeCondition = {