fix update prefix and profileId
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m45s

This commit is contained in:
Warunee Tamkoo 2026-02-27 15:24:51 +07:00
parent e4f46a1762
commit 2951630b7b
10 changed files with 201 additions and 69 deletions

View file

@ -64,6 +64,7 @@ import {
getRoleMappings,
removeUserRoles,
getToken,
updateUserAttributes,
} from "../keycloak";
import { ProfileEducation, CreateProfileEducation } from "../entities/ProfileEducation";
import { ProfileEducationHistory } from "../entities/ProfileEducationHistory";
@ -4242,6 +4243,12 @@ export class CommandController extends Controller {
profile.isActive = true;
}
await this.profileRepository.save(profile);
// update user attribute in keycloak
await updateUserAttributes(profile.keycloak ?? "", {
profileId: [profile.id],
prefix: [profile.prefix || ""],
});
// Task #2190
if (code && ["C-PM-17", "C-PM-18"].includes(code)) {
let organizeName = "";
@ -6329,8 +6336,7 @@ export class CommandController extends Controller {
name: x.name,
})),
);
}
else {
} else {
userKeycloakId = checkUser[0].id;
const rolesData = await getRoleMappings(userKeycloakId);
if (rolesData) {
@ -6393,9 +6399,9 @@ export class CommandController extends Controller {
if (profileEmployee.keycloak != null) {
// const delUserKeycloak = await deleteUser(profileEmployee.keycloak);
// if (delUserKeycloak) {
profileEmployee.keycloak = _null;
profileEmployee.roleKeycloaks = [];
profileEmployee.isActive = false;
profileEmployee.keycloak = _null;
profileEmployee.roleKeycloaks = [];
profileEmployee.isActive = false;
// }
}
profileEmployee.isLeave = true;
@ -6448,6 +6454,11 @@ export class CommandController extends Controller {
profile.phone = item.bodyProfile.phone ?? null;
await this.profileRepository.save(profile);
// update user attribute in keycloak
await updateUserAttributes(profile.keycloak ?? "", {
profileId: [profile.id],
prefix: [profile.prefix || ""],
});
setLogDataDiff(req, { before, after: profile });
}
//ขรก.ในระบบ หรือ ขรก.ในระบบที่สถานะพ้นจากราชการ

View file

@ -1,4 +1,3 @@
import { profile } from "console";
import { Controller, Get, Post, Query, Route, Security, Tags } from "tsoa";
import { calculateGovAge } from "../interfaces/utils";
import HttpSuccess from "../interfaces/http-success";
@ -14,7 +13,7 @@ export class AppController extends Controller {
@Post()
public async Post(@Query() profileId: string) {
const result = calculateGovAge(profileId,"OFFICER");
const result = calculateGovAge(profileId, "OFFICER");
return new HttpSuccess(result);
}
}

View file

@ -50,8 +50,13 @@ import {
getUserByUsername,
getRoles,
addUserRoles,
updateUserAttributes,
} from "../keycloak";
import { getPositionCountsAggregated, getPositionCount, PositionCountsByNode } from "../services/OrganizationService";
import {
getPositionCountsAggregated,
getPositionCount,
PositionCountsByNode,
} from "../services/OrganizationService";
import {
BatchSavePosMasterHistoryOfficer,
CreatePosMasterHistoryEmployee,
@ -4688,7 +4693,12 @@ export class OrganizationController extends Controller {
case 2: {
const data = await this.child1Repository.findOne({
where: { id: idNode },
relations: ["orgRevision", "orgChild2s", "orgChild2s.orgChild3s", "orgChild2s.orgChild3s.orgChild4s"],
relations: [
"orgRevision",
"orgChild2s",
"orgChild2s.orgChild3s",
"orgChild2s.orgChild3s.orgChild4s",
],
});
if (!data) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "not found child1Id");
@ -4700,7 +4710,9 @@ export class OrganizationController extends Controller {
deptID: data.id,
type: 2,
totalPositionCount: child1Counts.totalCount,
totalPositionVacant: isDraft ? child1Counts.nextVacantCount : child1Counts.currentVacantCount,
totalPositionVacant: isDraft
? child1Counts.nextVacantCount
: child1Counts.currentVacantCount,
children: this.buildOrgChild2s(data.orgChild2s, positionCounts, isDraft),
};
return new HttpSuccess([formattedData]);
@ -4720,7 +4732,9 @@ export class OrganizationController extends Controller {
deptID: data.id,
type: 3,
totalPositionCount: child2Counts.totalCount,
totalPositionVacant: isDraft ? child2Counts.nextVacantCount : child2Counts.currentVacantCount,
totalPositionVacant: isDraft
? child2Counts.nextVacantCount
: child2Counts.currentVacantCount,
children: this.buildOrgChild3s(data.orgChild3s, positionCounts, isDraft),
};
return new HttpSuccess([formattedData]);
@ -4740,7 +4754,9 @@ export class OrganizationController extends Controller {
deptID: data.id,
type: 4,
totalPositionCount: child3Counts.totalCount,
totalPositionVacant: isDraft ? child3Counts.nextVacantCount : child3Counts.currentVacantCount,
totalPositionVacant: isDraft
? child3Counts.nextVacantCount
: child3Counts.currentVacantCount,
children: this.buildOrgChild4s(data.orgChild4s, positionCounts, isDraft),
};
return new HttpSuccess([formattedData]);
@ -4760,7 +4776,9 @@ export class OrganizationController extends Controller {
deptID: data.id,
type: 5,
totalPositionCount: child4Counts.totalCount,
totalPositionVacant: isDraft ? child4Counts.nextVacantCount : child4Counts.currentVacantCount,
totalPositionVacant: isDraft
? child4Counts.nextVacantCount
: child4Counts.currentVacantCount,
};
return new HttpSuccess([formattedData]);
}
@ -8089,7 +8107,12 @@ export class OrganizationController extends Controller {
if (_item) {
_item.roleKeycloaks = Array.from(new Set([..._item.roleKeycloaks, ...roleKeycloak]));
check += 1;
await this.profileEmployeeRepo.save(_item);
_item = await this.profileEmployeeRepo.save(_item);
// update user attribute in keycloak
await updateUserAttributes(_item.keycloak, {
profileId: [_item.id],
prefix: [_item.prefix || ""],
});
}
} catch (error) {
console.error(`Error processing ${_item.citizenId}:`, error);
@ -9096,7 +9119,7 @@ export class OrganizationController extends Controller {
*/
private sumAllVacantCounts(
map: Map<string, { currentVacantCount: number; nextVacantCount: number }>,
isDraft: boolean
isDraft: boolean,
): number {
let sum = 0;
for (const value of map.values()) {
@ -9111,7 +9134,7 @@ export class OrganizationController extends Controller {
private buildOrgRoots(
orgRoots: OrgRoot[],
positionCounts: PositionCountsByNode,
isDraft: boolean
isDraft: boolean,
) {
if (!orgRoots) return [];
return orgRoots
@ -9135,7 +9158,7 @@ export class OrganizationController extends Controller {
private buildOrgChild1s(
orgChild1s: OrgChild1[],
positionCounts: PositionCountsByNode,
isDraft: boolean
isDraft: boolean,
) {
if (!orgChild1s) return [];
return orgChild1s
@ -9159,7 +9182,7 @@ export class OrganizationController extends Controller {
private buildOrgChild2s(
orgChild2s: OrgChild2[],
positionCounts: PositionCountsByNode,
isDraft: boolean
isDraft: boolean,
) {
if (!orgChild2s) return [];
return orgChild2s
@ -9183,7 +9206,7 @@ export class OrganizationController extends Controller {
private buildOrgChild3s(
orgChild3s: OrgChild3[],
positionCounts: PositionCountsByNode,
isDraft: boolean
isDraft: boolean,
) {
if (!orgChild3s) return [];
return orgChild3s
@ -9207,7 +9230,7 @@ export class OrganizationController extends Controller {
private buildOrgChild4s(
orgChild4s: OrgChild4[],
positionCounts: PositionCountsByNode,
isDraft: boolean
isDraft: boolean,
) {
if (!orgChild4s) return [];
return orgChild4s

View file

@ -116,7 +116,12 @@ export class ProfileChangeNameController extends Controller {
setLogDataDiff(req, { before, after: profile });
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -182,7 +187,12 @@ export class ProfileChangeNameController extends Controller {
// ปิดไว้ก่อนเพราะ error ต้องใช้ keycloak ที่มีสิทธิ์ในการ update //update 17/07
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -197,10 +207,7 @@ export class ProfileChangeNameController extends Controller {
* @param trainingId -
*/
@Patch("update-delete/{changeNameId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() changeNameId: string,
) {
public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) {
const record = await this.changeNameRepository.findOneBy({ id: changeNameId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {

View file

@ -122,7 +122,12 @@ export class ProfileChangeNameEmployeeController extends Controller {
setLogDataDiff(req, { before, after: profile });
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -195,16 +200,17 @@ export class ProfileChangeNameEmployeeController extends Controller {
* @param trainingId -
*/
@Patch("update-delete/{changeNameId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() changeNameId: string,
) {
public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) {
const record = await this.changeNameRepository.findOneBy({ id: changeNameId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {
return new HttpSuccess();
}
await new permission().PermissionOrgUserDelete(req, "SYS_REGISTRY_EMP", record.profileEmployeeId);
await new permission().PermissionOrgUserDelete(
req,
"SYS_REGISTRY_EMP",
record.profileEmployeeId,
);
const before = structuredClone(record);
const history = new ProfileChangeNameHistory();
const now = new Date();

View file

@ -114,7 +114,12 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
setLogDataDiff(req, { before, after: profile });
if (profile != null && profile.keycloak != null) {
const result = await updateName(profile.keycloak, profile.firstName, profile.lastName);
const result = await updateName(
profile.keycloak,
profile.firstName,
profile.lastName,
profile.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -187,10 +192,7 @@ export class ProfileChangeNameEmployeeTempController extends Controller {
* @param trainingId -
*/
@Patch("update-delete/{changeNameId}")
public async updateIsDeleted(
@Request() req: RequestWithUser,
@Path() changeNameId: string,
) {
public async updateIsDeleted(@Request() req: RequestWithUser, @Path() changeNameId: string) {
const record = await this.changeNameRepository.findOneBy({ id: changeNameId });
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
if (record.isDeleted === true) {

View file

@ -5092,7 +5092,12 @@ export class ProfileController extends Controller {
// setLogDataDiff(request, { before, after: record });
if (record != null && record.keycloak != null) {
const result = await updateName(record.keycloak, record.firstName, record.lastName);
const result = await updateName(
record.keycloak,
record.firstName,
record.lastName,
record.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -5406,7 +5411,12 @@ export class ProfileController extends Controller {
setLogDataDiff(request, { before, after: record });
if (record != null && record.keycloak != null) {
const result = await updateName(record.keycloak, record.firstName, record.lastName);
const result = await updateName(
record.keycloak,
record.firstName,
record.lastName,
record.prefix,
);
if (!result) {
throw new Error(result.errorMessage);
}
@ -5507,27 +5517,27 @@ export class ProfileController extends Controller {
@Get("history/user")
async getHistoryProfileByUser(@Request() request: RequestWithUser) {
const profile = await this.profileRepo.findOne({
where: { keycloak: request.user.sub }
where: { keycloak: request.user.sub },
});
if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
const profileHistory = await this.profileHistoryRepo.find({
where: { profileId: profile.id },
order: { createdAt: "ASC" }
order: { createdAt: "ASC" },
});
if (profileHistory.length == 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileHistory(), {
...profile,
birthDateOld: profile?.birthDate,
profileId: profile.id,
id: undefined
id: undefined,
}),
);
const firstRecord = await this.profileHistoryRepo.find({
where: { profileId: profile.id },
order: { createdAt: "ASC" }
order: { createdAt: "ASC" },
});
return new HttpSuccess(firstRecord);
}
@ -6482,27 +6492,27 @@ export class ProfileController extends Controller {
async getProfileHistory(@Path() id: string, @Request() req: RequestWithUser) {
//await new permission().PermissionOrgUserGet(req, "SYS_REGISTRY_OFFICER", id); //ไม่แน่ใจOFFปิดไว้ก่อน
const profile = await this.profileRepo.findOne({
where: { id: id }
where: { id: id },
});
if (!profile) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
const profileHistory = await this.profileHistoryRepo.find({
where: { profileId: id },
order: { createdAt: "ASC" }
order: { createdAt: "ASC" },
});
if (profileHistory.length == 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileHistory(), {
...profile,
birthDateOld: profile?.birthDate,
profileId: id,
id: undefined
id: undefined,
}),
);
const firstRecord = await this.profileHistoryRepo.find({
where: { profileId: id },
order: { createdAt: "ASC" }
order: { createdAt: "ASC" },
});
return new HttpSuccess(firstRecord);
}

View file

@ -33,6 +33,7 @@ import {
getUserByUsername,
changeUserPassword,
resetPassword,
updateUserAttributes,
} from "../keycloak";
import { AppDataSource } from "../database/data-source";
import { Profile } from "../entities/Profile";
@ -137,6 +138,13 @@ export class KeycloakController extends Controller {
}
profile.email = body.email == null ? _null : body.email;
await this.profileRepo.save(profile);
// Update Keycloak with profile prefix after profile is loaded
await updateUserAttributes(userId, {
profileId: [profile.id],
prefix: [profile.prefix || ""],
});
if (body.roles != null && body.roles.length > 0) {
const roleKeycloak = await this.roleKeycloakRepo.find({
where: { id: In(body.roles) },
@ -195,6 +203,12 @@ export class KeycloakController extends Controller {
}
profile.email = body.email == null ? _null : body.email;
await this.profileEmpRepo.save(profile);
// Update Keycloak with profile prefix after profile is loaded
await updateUserAttributes(userId, {
profileId: [profile.id],
prefix: [profile.prefix || ""],
});
if (body.roles != null && body.roles.length > 0) {
const roleKeycloak = await this.roleKeycloakRepo.find({
where: { id: In(body.roles) },
@ -475,14 +489,14 @@ export class KeycloakController extends Controller {
@Request() req: RequestWithUser,
@Body()
body: {
page: number,
pageSize: number,
keyword: string | null,
type: string,
isAll: boolean,
node: number | null,
nodeId: string | null,
}
page: number;
pageSize: number;
keyword: string | null;
type: string;
isAll: boolean;
node: number | null;
nodeId: string | null;
},
) {
let checkChildFromRole: any = {};
@ -558,10 +572,14 @@ export class KeycloakController extends Controller {
.andWhere(
new Brackets((qb) => {
qb.orWhere(
body.keyword != null && body.keyword != "" ? `profile.citizenId like '%${body.keyword}%'` : "1=1",
body.keyword != null && body.keyword != ""
? `profile.citizenId like '%${body.keyword}%'`
: "1=1",
)
.orWhere(
body.keyword != null && body.keyword != "" ? `profile.email like '%${body.keyword}%'` : "1=1",
body.keyword != null && body.keyword != ""
? `profile.email like '%${body.keyword}%'`
: "1=1",
)
.orWhere(
body.keyword != null && body.keyword != ""
@ -737,6 +755,12 @@ export class KeycloakController extends Controller {
}
profile.email = body.email == null ? _null : body.email;
await this.profileEmpRepo.save(profile);
// Update Keycloak with profile prefix after profile is loaded
await updateUserAttributes(userId, {
profileId: [profile.id],
prefix: [profile.prefix || ""],
});
if (body.roles != null && body.roles.length > 0) {
const roleKeycloak = await this.roleKeycloakRepo.find({
where: { id: In(body.roles) },
@ -783,7 +807,6 @@ export class KeycloakController extends Controller {
@Get("user/role/{id}")
async getRoleUser(@Request() req: RequestWithUser, @Path("id") id: string) {
const profile = await this.profileRepo.findOne({
where: { keycloak: id },
relations: ["roleKeycloaks"],
@ -791,8 +814,8 @@ export class KeycloakController extends Controller {
if (
req.user.sub === id &&
req.user.role.some(x => x === 'ADMIN') &&
!req.user.role.some(x => x === 'SUPER_ADMIN')
req.user.role.some((x) => x === "ADMIN") &&
!req.user.role.some((x) => x === "SUPER_ADMIN")
) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่มีสิทธิ์เข้าถึงข้อมูลนี้");
}

View file

@ -298,6 +298,7 @@ export async function updateName(
userId: string,
firstName: string,
lastName: string,
prefix: string,
// opts: Record<string, any>,
) {
// const { password, ...rest } = opts;
@ -315,6 +316,9 @@ export async function updateName(
// ...rest,
firstName,
lastName,
attributes: {
prefix,
},
}),
}).catch((e) => console.log("Keycloak Error: ", e));
@ -971,3 +975,44 @@ export async function getAllUsersPaginated(
enabled: v.enabled === true || v.enabled === "true",
}));
}
/**
* Create keycloak user by given username and password with roles
*
* Client must have permission to manage realm's user
*
* @returns user uuid or true if success, false otherwise.
*/
export async function createUserHaveProfile(
username: string,
password: string,
profileId: string,
prefix: string,
opts?: Record<string, any>,
token?: string,
) {
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, {
// prettier-ignore
headers: {
"authorization": `Bearer ${token || await getToken()}`,
"content-type": `application/json`,
},
method: "POST",
body: JSON.stringify({
enabled: true,
credentials: [{ type: "password", value: password, temporary: false }],
username,
...opts,
}),
}).catch((e) => console.log("Keycloak Error: ", e));
if (!res) return false;
if (!res.ok) {
// return Boolean(console.error("Keycloak Error Response: ", await res.json()));
return await res.json();
}
const path = res.headers.get("Location");
const id = path?.split("/").at(-1);
return id || true;
}

View file

@ -5,7 +5,7 @@ import { ProfileEmployee } from "../entities/ProfileEmployee";
// import { EmployeePosMaster } from "../entities/EmployeePosMaster";
// import { OrgRoot } from "../entities/OrgRoot";
import {
createUser,
createUserHaveProfile,
getUser,
getUserByUsername,
updateUserAttributes,
@ -809,12 +809,18 @@ export class KeycloakAttributeService {
}
// Create new user in Keycloak
const createResult = await createUser(profile.citizenId, "P@ssw0rd", {
firstName: profile.firstName || "",
lastName: profile.lastName || "",
email: profile.email || undefined,
enabled: true,
});
const createResult = await createUserHaveProfile(
profile.citizenId,
"P@ssw0rd",
profile.id,
profile.prefix,
{
firstName: profile.firstName || "",
lastName: profile.lastName || "",
email: profile.email || undefined,
enabled: true,
},
);
if (!createResult || typeof createResult !== "string") {
return {