fix emp temp and add script clear org dna at keycloak
This commit is contained in:
parent
b714dfe239
commit
49a8494a8d
3 changed files with 363 additions and 57 deletions
|
|
@ -187,6 +187,48 @@ export class KeycloakSyncController extends Controller {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear org DNA attributes for profiles (Admin only)
|
||||
*
|
||||
* @summary Clear org DNA attributes in Keycloak for given profiles (ADMIN)
|
||||
*
|
||||
* @description
|
||||
* This endpoint will:
|
||||
* - Clear all org DNA fields (orgRootDnaId, orgChild1-4DnaId) by setting them to empty strings
|
||||
* - Use when an employee leaves their position (current_holderId becomes null)
|
||||
*
|
||||
* @param {request} request Request body containing profileIds array and profileType
|
||||
*/
|
||||
@Post("clear-org-dna")
|
||||
async clearOrgDna(
|
||||
@Body() request: { profileIds: string[]; profileType: "PROFILE" | "PROFILE_EMPLOYEE" },
|
||||
) {
|
||||
const { profileIds, profileType } = request;
|
||||
|
||||
// Validate profileIds
|
||||
if (!profileIds || profileIds.length === 0) {
|
||||
throw new HttpError(HttpStatus.BAD_REQUEST, "profileIds ต้องไม่ว่างเปล่า");
|
||||
}
|
||||
|
||||
// Validate profileType
|
||||
if (!["PROFILE", "PROFILE_EMPLOYEE"].includes(profileType)) {
|
||||
throw new HttpError(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"profileType ต้องเป็น PROFILE หรือ PROFILE_EMPLOYEE เท่านั้น",
|
||||
);
|
||||
}
|
||||
|
||||
const result = await this.keycloakAttributeService.clearOrgDnaAttributes(
|
||||
profileIds,
|
||||
profileType,
|
||||
);
|
||||
|
||||
return new HttpSuccess({
|
||||
message: "Clear org DNA attributes เสร็จสิ้น",
|
||||
...result,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch sync all users (Admin only)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { PosMaster } from "./../entities/PosMaster";
|
|||
import axios from "axios";
|
||||
import { KeycloakSyncController } from "./KeycloakSyncController";
|
||||
import { EmployeePosMaster } from "./../entities/EmployeePosMaster";
|
||||
import { EmployeeTempPosMaster } from "./../entities/EmployeeTempPosMaster";
|
||||
|
||||
interface OrgUpdatePayload {
|
||||
profileId: string;
|
||||
|
|
@ -26,6 +27,7 @@ interface OrgUpdatePayload {
|
|||
export class ScriptProfileOrgController extends Controller {
|
||||
private posMasterRepo = AppDataSource.getRepository(PosMaster);
|
||||
private employeePosMasterRepo = AppDataSource.getRepository(EmployeePosMaster);
|
||||
private employeeTempPosMasterRepo = AppDataSource.getRepository(EmployeeTempPosMaster);
|
||||
|
||||
// Idempotency flag to prevent concurrent runs
|
||||
private isRunning = false;
|
||||
|
|
@ -37,6 +39,11 @@ export class ScriptProfileOrgController extends Controller {
|
|||
10,
|
||||
);
|
||||
|
||||
/**
|
||||
* Script to update profile's organizational structure in leave service and sync to Keycloak
|
||||
*
|
||||
* @summary Update org structure for profiles updated within a certain time window and sync to Keycloak
|
||||
*/
|
||||
@Post("update-org")
|
||||
public async cronjobUpdateOrg(@Request() request: RequestWithUser) {
|
||||
// Idempotency check - prevent concurrent runs
|
||||
|
|
@ -61,7 +68,7 @@ export class ScriptProfileOrgController extends Controller {
|
|||
});
|
||||
|
||||
// Query with optimized select - only fetch required fields
|
||||
const [posMasters, posMasterEmployee] = await Promise.all([
|
||||
const [posMasters, posMasterEmployee, posMasterEmployeeTemp] = await Promise.all([
|
||||
this.posMasterRepo.find({
|
||||
where: {
|
||||
lastUpdatedAt: MoreThanOrEqual(windowStart),
|
||||
|
|
@ -120,16 +127,46 @@ export class ScriptProfileOrgController extends Controller {
|
|||
current_holder: { id: true },
|
||||
},
|
||||
}),
|
||||
this.employeeTempPosMasterRepo.find({
|
||||
where: {
|
||||
lastUpdatedAt: MoreThanOrEqual(windowStart),
|
||||
orgRevision: {
|
||||
orgRevisionIsCurrent: true,
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
"orgRevision",
|
||||
"orgRoot",
|
||||
"orgChild1",
|
||||
"orgChild2",
|
||||
"orgChild3",
|
||||
"orgChild4",
|
||||
"current_holder",
|
||||
],
|
||||
select: {
|
||||
id: true,
|
||||
current_holderId: true,
|
||||
lastUpdatedAt: true,
|
||||
orgRevision: { id: true },
|
||||
orgRoot: { ancestorDNA: true },
|
||||
orgChild1: { ancestorDNA: true },
|
||||
orgChild2: { ancestorDNA: true },
|
||||
orgChild3: { ancestorDNA: true },
|
||||
orgChild4: { ancestorDNA: true },
|
||||
current_holder: { id: true },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
console.log("cronjobUpdateOrg: Database query completed", {
|
||||
posMastersCount: posMasters.length,
|
||||
employeePosCount: posMasterEmployee.length,
|
||||
totalRecords: posMasters.length + posMasterEmployee.length,
|
||||
employeeTempPosCount: posMasterEmployeeTemp.length,
|
||||
totalRecords: posMasters.length + posMasterEmployee.length + posMasterEmployeeTemp.length,
|
||||
});
|
||||
|
||||
// Build payloads with proper profile type tracking
|
||||
const payloads = this.buildPayloads(posMasters, posMasterEmployee);
|
||||
const payloads = this.buildPayloads(posMasters, posMasterEmployee, posMasterEmployeeTemp);
|
||||
|
||||
if (payloads.length === 0) {
|
||||
console.log("cronjobUpdateOrg: No records to process");
|
||||
|
|
@ -246,12 +283,13 @@ export class ScriptProfileOrgController extends Controller {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build payloads from PosMaster and EmployeePosMaster records
|
||||
* Build payloads from PosMaster, EmployeePosMaster, and EmployeeTempPosMaster records
|
||||
* Includes proper profile type tracking for accurate Keycloak sync
|
||||
*/
|
||||
private buildPayloads(
|
||||
posMasters: PosMaster[],
|
||||
posMasterEmployee: EmployeePosMaster[],
|
||||
posMasterEmployeeTemp: EmployeeTempPosMaster[],
|
||||
): OrgUpdatePayload[] {
|
||||
const payloads: OrgUpdatePayload[] = [];
|
||||
|
||||
|
|
@ -285,6 +323,21 @@ export class ScriptProfileOrgController extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
// Process EmployeeTempPosMaster records (PROFILE_EMPLOYEE type)
|
||||
for (const employeeTempPos of posMasterEmployeeTemp) {
|
||||
if (employeeTempPos.current_holder && employeeTempPos.current_holderId) {
|
||||
payloads.push({
|
||||
profileId: employeeTempPos.current_holderId,
|
||||
rootDnaId: employeeTempPos.orgRoot?.ancestorDNA || null,
|
||||
child1DnaId: employeeTempPos.orgChild1?.ancestorDNA || null,
|
||||
child2DnaId: employeeTempPos.orgChild2?.ancestorDNA || null,
|
||||
child3DnaId: employeeTempPos.orgChild3?.ancestorDNA || null,
|
||||
child4DnaId: employeeTempPos.orgChild4?.ancestorDNA || null,
|
||||
profileType: "PROFILE_EMPLOYEE",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return payloads;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,40 +88,101 @@ export class KeycloakAttributeService {
|
|||
}
|
||||
|
||||
// If not found in Profile, try ProfileEmployee (ลูกจ้าง)
|
||||
const profileEmployeeResult = await this.profileEmployeeRepo
|
||||
// First, get the profileEmployee to check employeeClass
|
||||
const profileEmployeeBasic = await this.profileEmployeeRepo
|
||||
.createQueryBuilder("pe")
|
||||
.leftJoinAndSelect("pe.current_holders", "epm")
|
||||
.leftJoinAndSelect("epm.orgRoot", "org")
|
||||
.leftJoinAndSelect("epm.orgChild1", "orgChild1")
|
||||
.leftJoinAndSelect("epm.orgChild2", "orgChild2")
|
||||
.leftJoinAndSelect("epm.orgChild3", "orgChild3")
|
||||
.leftJoinAndSelect("epm.orgChild4", "orgChild4")
|
||||
.where("pe.keycloak = :keycloakUserId", { keycloakUserId })
|
||||
.getOne();
|
||||
|
||||
if (
|
||||
profileEmployeeResult &&
|
||||
profileEmployeeResult.current_holders &&
|
||||
profileEmployeeResult.current_holders.length > 0
|
||||
) {
|
||||
const currentPos = profileEmployeeResult.current_holders[0];
|
||||
const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || "";
|
||||
const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || "";
|
||||
const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || "";
|
||||
const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || "";
|
||||
const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || "";
|
||||
if (!profileEmployeeBasic) {
|
||||
// Return null values if no profile found
|
||||
return {
|
||||
profileId: profileEmployeeResult.id,
|
||||
orgRootDnaId,
|
||||
orgChild1DnaId,
|
||||
orgChild2DnaId,
|
||||
orgChild3DnaId,
|
||||
orgChild4DnaId,
|
||||
empType: profileEmployeeResult.employeeClass,
|
||||
prefix: profileEmployeeResult.prefix,
|
||||
profileId: null,
|
||||
orgRootDnaId: null,
|
||||
orgChild1DnaId: null,
|
||||
orgChild2DnaId: null,
|
||||
orgChild3DnaId: null,
|
||||
orgChild4DnaId: null,
|
||||
empType: null,
|
||||
prefix: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Check employeeClass to determine which table to query
|
||||
const isPermEmployee = profileEmployeeBasic.employeeClass === "PERM";
|
||||
|
||||
if (isPermEmployee) {
|
||||
// ลูกจ้างประจำ (PERM) - ใช้ EmployeePosMaster
|
||||
const profileEmployeeResult = await this.profileEmployeeRepo
|
||||
.createQueryBuilder("pe")
|
||||
.leftJoinAndSelect("pe.current_holders", "epm")
|
||||
.leftJoinAndSelect("epm.orgRoot", "org")
|
||||
.leftJoinAndSelect("epm.orgChild1", "orgChild1")
|
||||
.leftJoinAndSelect("epm.orgChild2", "orgChild2")
|
||||
.leftJoinAndSelect("epm.orgChild3", "orgChild3")
|
||||
.leftJoinAndSelect("epm.orgChild4", "orgChild4")
|
||||
.where("pe.keycloak = :keycloakUserId", { keycloakUserId })
|
||||
.getOne();
|
||||
|
||||
if (
|
||||
profileEmployeeResult &&
|
||||
profileEmployeeResult.current_holders &&
|
||||
profileEmployeeResult.current_holders.length > 0
|
||||
) {
|
||||
const currentPos = profileEmployeeResult.current_holders[0];
|
||||
const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || "";
|
||||
const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || "";
|
||||
const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || "";
|
||||
const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || "";
|
||||
const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || "";
|
||||
return {
|
||||
profileId: profileEmployeeResult.id,
|
||||
orgRootDnaId,
|
||||
orgChild1DnaId,
|
||||
orgChild2DnaId,
|
||||
orgChild3DnaId,
|
||||
orgChild4DnaId,
|
||||
empType: profileEmployeeResult.employeeClass,
|
||||
prefix: profileEmployeeResult.prefix,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// ลูกจ้างชั่วคราว (TEMP) - ใช้ EmployeeTempPosMaster
|
||||
const profileEmployeeResult = await this.profileEmployeeRepo
|
||||
.createQueryBuilder("pe")
|
||||
.leftJoinAndSelect("pe.current_holderTemps", "etpm")
|
||||
.leftJoinAndSelect("etpm.orgRoot", "org")
|
||||
.leftJoinAndSelect("etpm.orgChild1", "orgChild1")
|
||||
.leftJoinAndSelect("etpm.orgChild2", "orgChild2")
|
||||
.leftJoinAndSelect("etpm.orgChild3", "orgChild3")
|
||||
.leftJoinAndSelect("etpm.orgChild4", "orgChild4")
|
||||
.where("pe.keycloak = :keycloakUserId", { keycloakUserId })
|
||||
.getOne();
|
||||
|
||||
if (
|
||||
profileEmployeeResult &&
|
||||
profileEmployeeResult.current_holderTemps &&
|
||||
profileEmployeeResult.current_holderTemps.length > 0
|
||||
) {
|
||||
const currentPos = profileEmployeeResult.current_holderTemps[0];
|
||||
const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || "";
|
||||
const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || "";
|
||||
const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || "";
|
||||
const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || "";
|
||||
const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || "";
|
||||
return {
|
||||
profileId: profileEmployeeResult.id,
|
||||
orgRootDnaId,
|
||||
orgChild1DnaId,
|
||||
orgChild2DnaId,
|
||||
orgChild3DnaId,
|
||||
orgChild4DnaId,
|
||||
empType: profileEmployeeResult.employeeClass,
|
||||
prefix: profileEmployeeResult.prefix,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Return null values if no profile found
|
||||
return {
|
||||
profileId: null,
|
||||
|
|
@ -187,40 +248,101 @@ export class KeycloakAttributeService {
|
|||
};
|
||||
}
|
||||
} else {
|
||||
const profileEmployeeResult = await this.profileEmployeeRepo
|
||||
// First, get the profileEmployee to check employeeClass
|
||||
const profileEmployeeBasic = await this.profileEmployeeRepo
|
||||
.createQueryBuilder("pe")
|
||||
.leftJoinAndSelect("pe.current_holders", "epm")
|
||||
.leftJoinAndSelect("epm.orgRoot", "org")
|
||||
.leftJoinAndSelect("pm.orgChild1", "orgChild1")
|
||||
.leftJoinAndSelect("pm.orgChild2", "orgChild2")
|
||||
.leftJoinAndSelect("pm.orgChild3", "orgChild3")
|
||||
.leftJoinAndSelect("pm.orgChild4", "orgChild4")
|
||||
.where("pe.id = :profileId", { profileId })
|
||||
.getOne();
|
||||
|
||||
if (
|
||||
profileEmployeeResult &&
|
||||
profileEmployeeResult.current_holders &&
|
||||
profileEmployeeResult.current_holders.length > 0
|
||||
) {
|
||||
const currentPos = profileEmployeeResult.current_holders[0];
|
||||
const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || "";
|
||||
const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || "";
|
||||
const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || "";
|
||||
const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || "";
|
||||
const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || "";
|
||||
|
||||
if (!profileEmployeeBasic) {
|
||||
return {
|
||||
profileId: profileEmployeeResult.id,
|
||||
orgRootDnaId,
|
||||
orgChild1DnaId,
|
||||
orgChild2DnaId,
|
||||
orgChild3DnaId,
|
||||
orgChild4DnaId,
|
||||
empType: profileEmployeeResult.employeeClass,
|
||||
prefix: profileEmployeeResult.prefix,
|
||||
profileId: null,
|
||||
orgRootDnaId: null,
|
||||
orgChild1DnaId: null,
|
||||
orgChild2DnaId: null,
|
||||
orgChild3DnaId: null,
|
||||
orgChild4DnaId: null,
|
||||
empType: null,
|
||||
prefix: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Check employeeClass to determine which table to query
|
||||
const isPermEmployee = profileEmployeeBasic.employeeClass === "PERM";
|
||||
|
||||
if (isPermEmployee) {
|
||||
// ลูกจ้างประจำ (PERM) - ใช้ EmployeePosMaster
|
||||
const profileEmployeeResult = await this.profileEmployeeRepo
|
||||
.createQueryBuilder("pe")
|
||||
.leftJoinAndSelect("pe.current_holders", "epm")
|
||||
.leftJoinAndSelect("epm.orgRoot", "org")
|
||||
.leftJoinAndSelect("epm.orgChild1", "orgChild1")
|
||||
.leftJoinAndSelect("epm.orgChild2", "orgChild2")
|
||||
.leftJoinAndSelect("epm.orgChild3", "orgChild3")
|
||||
.leftJoinAndSelect("epm.orgChild4", "orgChild4")
|
||||
.where("pe.id = :profileId", { profileId })
|
||||
.getOne();
|
||||
|
||||
if (
|
||||
profileEmployeeResult &&
|
||||
profileEmployeeResult.current_holders &&
|
||||
profileEmployeeResult.current_holders.length > 0
|
||||
) {
|
||||
const currentPos = profileEmployeeResult.current_holders[0];
|
||||
const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || "";
|
||||
const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || "";
|
||||
const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || "";
|
||||
const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || "";
|
||||
const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || "";
|
||||
|
||||
return {
|
||||
profileId: profileEmployeeResult.id,
|
||||
orgRootDnaId,
|
||||
orgChild1DnaId,
|
||||
orgChild2DnaId,
|
||||
orgChild3DnaId,
|
||||
orgChild4DnaId,
|
||||
empType: profileEmployeeResult.employeeClass,
|
||||
prefix: profileEmployeeResult.prefix,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// ลูกจ้างชั่วคราว (TEMP) - ใช้ EmployeeTempPosMaster
|
||||
const profileEmployeeResult = await this.profileEmployeeRepo
|
||||
.createQueryBuilder("pe")
|
||||
.leftJoinAndSelect("pe.current_holderTemps", "etpm")
|
||||
.leftJoinAndSelect("etpm.orgRoot", "org")
|
||||
.leftJoinAndSelect("etpm.orgChild1", "orgChild1")
|
||||
.leftJoinAndSelect("etpm.orgChild2", "orgChild2")
|
||||
.leftJoinAndSelect("etpm.orgChild3", "orgChild3")
|
||||
.leftJoinAndSelect("etpm.orgChild4", "orgChild4")
|
||||
.where("pe.id = :profileId", { profileId })
|
||||
.getOne();
|
||||
|
||||
if (
|
||||
profileEmployeeResult &&
|
||||
profileEmployeeResult.current_holderTemps &&
|
||||
profileEmployeeResult.current_holderTemps.length > 0
|
||||
) {
|
||||
const currentPos = profileEmployeeResult.current_holderTemps[0];
|
||||
const orgRootDnaId = currentPos.orgRoot?.ancestorDNA || "";
|
||||
const orgChild1DnaId = currentPos.orgChild1?.ancestorDNA || "";
|
||||
const orgChild2DnaId = currentPos.orgChild2?.ancestorDNA || "";
|
||||
const orgChild3DnaId = currentPos.orgChild3?.ancestorDNA || "";
|
||||
const orgChild4DnaId = currentPos.orgChild4?.ancestorDNA || "";
|
||||
|
||||
return {
|
||||
profileId: profileEmployeeResult.id,
|
||||
orgRootDnaId,
|
||||
orgChild1DnaId,
|
||||
orgChild2DnaId,
|
||||
orgChild3DnaId,
|
||||
orgChild4DnaId,
|
||||
empType: profileEmployeeResult.employeeClass,
|
||||
prefix: profileEmployeeResult.prefix,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -313,6 +435,95 @@ export class KeycloakAttributeService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear org DNA attributes in Keycloak for given profiles
|
||||
* Sets all org DNA fields to empty strings
|
||||
*
|
||||
* @param profileIds - Array of profile IDs to clear
|
||||
* @param profileType - 'PROFILE' for officers or 'PROFILE_EMPLOYEE' for employees
|
||||
* @returns Object with success/failed counts and details
|
||||
*/
|
||||
async clearOrgDnaAttributes(
|
||||
profileIds: string[],
|
||||
profileType: "PROFILE" | "PROFILE_EMPLOYEE",
|
||||
): Promise<{
|
||||
total: number;
|
||||
success: number;
|
||||
failed: number;
|
||||
details: Array<{ profileId: string; status: "success" | "failed"; error?: string }>;
|
||||
}> {
|
||||
const result = {
|
||||
total: profileIds.length,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
details: [] as Array<{ profileId: string; status: "success" | "failed"; error?: string }>,
|
||||
};
|
||||
|
||||
for (const profileId of profileIds) {
|
||||
try {
|
||||
// Get the keycloak userId from the profile
|
||||
let keycloakUserId: string | null = null;
|
||||
|
||||
if (profileType === "PROFILE") {
|
||||
const profile = await this.profileRepo.findOne({ where: { id: profileId } });
|
||||
keycloakUserId = profile?.keycloak || "";
|
||||
} else {
|
||||
const profileEmployee = await this.profileEmployeeRepo.findOne({
|
||||
where: { id: profileId },
|
||||
});
|
||||
keycloakUserId = profileEmployee?.keycloak || "";
|
||||
}
|
||||
|
||||
if (!keycloakUserId) {
|
||||
result.failed++;
|
||||
result.details.push({
|
||||
profileId,
|
||||
status: "failed",
|
||||
error: "No Keycloak user ID found",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clear org DNA attributes by setting them to empty strings
|
||||
const clearedAttributes: Record<string, string[]> = {
|
||||
orgRootDnaId: [""],
|
||||
orgChild1DnaId: [""],
|
||||
orgChild2DnaId: [""],
|
||||
orgChild3DnaId: [""],
|
||||
orgChild4DnaId: [""],
|
||||
};
|
||||
|
||||
const success = await updateUserAttributes(keycloakUserId, clearedAttributes);
|
||||
|
||||
if (success) {
|
||||
result.success++;
|
||||
result.details.push({
|
||||
profileId,
|
||||
status: "success",
|
||||
});
|
||||
console.log(`Cleared org DNA attributes for profile ${profileId} (${profileType})`);
|
||||
} else {
|
||||
result.failed++;
|
||||
result.details.push({
|
||||
profileId,
|
||||
status: "failed",
|
||||
error: "Failed to update Keycloak attributes",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
result.failed++;
|
||||
result.details.push({
|
||||
profileId,
|
||||
status: "failed",
|
||||
error: error.message || "Unknown error",
|
||||
});
|
||||
console.error(`Error clearing org DNA attributes for profile ${profileId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch sync multiple users with unlimited count and parallel processing
|
||||
* Useful for initial sync or periodic updates
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue