Merge branch 'develop' into adiDev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m3s
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m3s
This commit is contained in:
commit
8c9a62a378
6 changed files with 602 additions and 22 deletions
|
|
@ -325,7 +325,123 @@ export class ApiManageController extends Controller {
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น
|
private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น
|
||||||
private readonly EXCLUDED_COLUMNS = ["createdUserId", "lastUpdateUserId"]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์
|
private readonly EXCLUDED_COLUMNS = [
|
||||||
|
"createdUserId",
|
||||||
|
"lastUpdateUserId",
|
||||||
|
"createdAt",
|
||||||
|
"createdFullName",
|
||||||
|
"lastUpdateFullName",
|
||||||
|
"avatarName",
|
||||||
|
"profileId",
|
||||||
|
"prefixId",
|
||||||
|
"profileEmployeeId",
|
||||||
|
"documentId",
|
||||||
|
"orgRevisionId",
|
||||||
|
"posMasterId",
|
||||||
|
"orgRootId",
|
||||||
|
"orgChild1Id",
|
||||||
|
"orgChild2Id",
|
||||||
|
"orgChild3Id",
|
||||||
|
"orgChild4Id",
|
||||||
|
"ancestorDNA",
|
||||||
|
"keycloak",
|
||||||
|
"commandId",
|
||||||
|
"prefixMain",
|
||||||
|
"authRoleId",
|
||||||
|
"next_holderId",
|
||||||
|
"current_holderId",
|
||||||
|
]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์
|
||||||
|
|
||||||
|
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity
|
||||||
|
private readonly PROFILE_FIELD_REPLACEMENTS: Record<
|
||||||
|
string,
|
||||||
|
{ propertyName: string; type: string; comment: string; joinTable: string; joinField: string }
|
||||||
|
> = {
|
||||||
|
posLevelId: {
|
||||||
|
propertyName: "posLevelName",
|
||||||
|
type: "string",
|
||||||
|
comment: "ระดับตำแหน่ง",
|
||||||
|
joinTable: "PosLevel",
|
||||||
|
joinField: "posLevelName",
|
||||||
|
},
|
||||||
|
posTypeId: {
|
||||||
|
propertyName: "posTypeName",
|
||||||
|
type: "string",
|
||||||
|
comment: "ประเภทตำแหน่ง",
|
||||||
|
joinTable: "PosType",
|
||||||
|
joinField: "posTypeName",
|
||||||
|
},
|
||||||
|
registrationProvinceId: {
|
||||||
|
propertyName: "registrationProvinceName",
|
||||||
|
type: "string",
|
||||||
|
comment: "จังหวัดตามทะเบียนบ้าน",
|
||||||
|
joinTable: "Province",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
registrationDistrictId: {
|
||||||
|
propertyName: "registrationDistrictName",
|
||||||
|
type: "string",
|
||||||
|
comment: "เขตตามทะเบียนบ้าน",
|
||||||
|
joinTable: "District",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
registrationSubDistrictId: {
|
||||||
|
propertyName: "registrationSubDistrictName",
|
||||||
|
type: "string",
|
||||||
|
comment: "แขวงตามทะเบียนบ้าน",
|
||||||
|
joinTable: "SubDistrict",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
currentProvinceId: {
|
||||||
|
propertyName: "currentProvinceName",
|
||||||
|
type: "string",
|
||||||
|
comment: "จังหวัดตามปัจจุบัน",
|
||||||
|
joinTable: "Province",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
currentDistrictId: {
|
||||||
|
propertyName: "currentDistrictName",
|
||||||
|
type: "string",
|
||||||
|
comment: "เขตตามปัจจุบัน",
|
||||||
|
joinTable: "District",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
currentSubDistrictId: {
|
||||||
|
propertyName: "currentSubDistrictName",
|
||||||
|
type: "string",
|
||||||
|
comment: "แขวงตามปัจจุบัน",
|
||||||
|
joinTable: "SubDistrict",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity
|
||||||
|
private readonly POSITION_FIELD_REPLACEMENTS: Record<
|
||||||
|
string,
|
||||||
|
{ propertyName: string; type: string; comment: string; joinTable: string; joinField: string }
|
||||||
|
> = {
|
||||||
|
posTypeId: {
|
||||||
|
propertyName: "posTypeName",
|
||||||
|
type: "string",
|
||||||
|
comment: "ประเภทตำแหน่ง",
|
||||||
|
joinTable: "PosType",
|
||||||
|
joinField: "posTypeName",
|
||||||
|
},
|
||||||
|
posLevelId: {
|
||||||
|
propertyName: "posLevelName",
|
||||||
|
type: "string",
|
||||||
|
comment: "ระดับตำแหน่ง",
|
||||||
|
joinTable: "PosLevel",
|
||||||
|
joinField: "posLevelName",
|
||||||
|
},
|
||||||
|
posExecutiveId: {
|
||||||
|
propertyName: "posExecutiveName",
|
||||||
|
type: "string",
|
||||||
|
comment: "ตำแหน่งทางการบริหาร",
|
||||||
|
joinTable: "PosExecutive",
|
||||||
|
joinField: "posExecutiveName",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
private validateSuperAdminRole(user: any): void {
|
private validateSuperAdminRole(user: any): void {
|
||||||
if (!user.role.includes("SUPER_ADMIN")) {
|
if (!user.role.includes("SUPER_ADMIN")) {
|
||||||
|
|
@ -364,11 +480,8 @@ export class ApiManageController extends Controller {
|
||||||
|
|
||||||
const result = this.entities
|
const result = this.entities
|
||||||
.filter((entity) => entity.system.includes(system))
|
.filter((entity) => entity.system.includes(system))
|
||||||
.map(({ name, repository, description, isMain }) => ({
|
.map(({ name, repository, description, isMain }) => {
|
||||||
tb: name,
|
let columns = repository.metadata.columns
|
||||||
description,
|
|
||||||
isMain: isMain || false,
|
|
||||||
propertys: repository.metadata.columns
|
|
||||||
.filter(
|
.filter(
|
||||||
(column: any) =>
|
(column: any) =>
|
||||||
!column.isPrimary && !this.EXCLUDED_COLUMNS.includes(column.propertyName),
|
!column.isPrimary && !this.EXCLUDED_COLUMNS.includes(column.propertyName),
|
||||||
|
|
@ -378,8 +491,74 @@ export class ApiManageController extends Controller {
|
||||||
type: typeof column.type === "string" ? column.type : "string",
|
type: typeof column.type === "string" ? column.type : "string",
|
||||||
comment: column.comment,
|
comment: column.comment,
|
||||||
key: column.propertyName,
|
key: column.propertyName,
|
||||||
})),
|
}));
|
||||||
}));
|
|
||||||
|
// Special handling for Profile entity - replace ID fields with name fields
|
||||||
|
if (name === "Profile") {
|
||||||
|
const replacementKeys = Object.keys(this.PROFILE_FIELD_REPLACEMENTS);
|
||||||
|
|
||||||
|
// Remove ID fields that should be replaced
|
||||||
|
columns = columns.filter(
|
||||||
|
(col: { propertyName: string }) => !replacementKeys.includes(col.propertyName),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the corresponding name fields
|
||||||
|
const nameFields = replacementKeys.map((key) => ({
|
||||||
|
propertyName: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName,
|
||||||
|
type: "string",
|
||||||
|
comment: this.PROFILE_FIELD_REPLACEMENTS[key].comment,
|
||||||
|
key: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
columns = [...columns, ...nameFields];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for Position entity - replace ID fields with name fields
|
||||||
|
if (name === "Position") {
|
||||||
|
const replacementKeys = Object.keys(this.POSITION_FIELD_REPLACEMENTS);
|
||||||
|
|
||||||
|
// Remove ID fields that should be replaced
|
||||||
|
columns = columns.filter(
|
||||||
|
(col: { propertyName: string }) => !replacementKeys.includes(col.propertyName),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the corresponding name fields
|
||||||
|
const nameFields = replacementKeys.map((key) => ({
|
||||||
|
propertyName: this.POSITION_FIELD_REPLACEMENTS[key].propertyName,
|
||||||
|
type: "string",
|
||||||
|
comment: this.POSITION_FIELD_REPLACEMENTS[key].comment,
|
||||||
|
key: this.POSITION_FIELD_REPLACEMENTS[key].propertyName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
columns = [...columns, ...nameFields];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for PosMaster entity - add Profile fields for holder information
|
||||||
|
if (name === "PosMaster") {
|
||||||
|
// Add Profile fields that are accessible via current_holder relation
|
||||||
|
const profileFields = ["prefix", "rank", "firstName", "lastName", "citizenId"];
|
||||||
|
const profileRepository = AppDataSource.getRepository(Profile);
|
||||||
|
const profileColumns = profileRepository.metadata.columns
|
||||||
|
.filter(
|
||||||
|
(column: any) => !column.isPrimary && profileFields.includes(column.propertyName),
|
||||||
|
)
|
||||||
|
.map((column: any) => ({
|
||||||
|
propertyName: `Profile.${column.propertyName}`,
|
||||||
|
type: typeof column.type === "string" ? column.type : "string",
|
||||||
|
comment: column.comment,
|
||||||
|
key: `Profile.${column.propertyName}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
columns = [...columns, ...profileColumns];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tb: name,
|
||||||
|
description,
|
||||||
|
isMain: isMain || false,
|
||||||
|
propertys: columns,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return new HttpSuccess(result);
|
return new HttpSuccess(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,153 @@ export class ApiWebServiceController extends Controller {
|
||||||
private apiNameRepository = AppDataSource.getRepository(ApiName);
|
private apiNameRepository = AppDataSource.getRepository(ApiName);
|
||||||
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
|
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
|
||||||
private apiHistoryRepository = AppDataSource.getRepository(ApiHistory);
|
private apiHistoryRepository = AppDataSource.getRepository(ApiHistory);
|
||||||
|
private currentRevisionId: string = "";
|
||||||
|
|
||||||
|
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity
|
||||||
|
private readonly PROFILE_FIELD_REPLACEMENTS: Record<
|
||||||
|
string,
|
||||||
|
{ propertyName: string; joinRelation: string; joinField: string }
|
||||||
|
> = {
|
||||||
|
posLevelName: {
|
||||||
|
propertyName: "posLevelId",
|
||||||
|
joinRelation: "posLevel",
|
||||||
|
joinField: "posLevelName",
|
||||||
|
},
|
||||||
|
posTypeName: {
|
||||||
|
propertyName: "posTypeId",
|
||||||
|
joinRelation: "posType",
|
||||||
|
joinField: "posTypeName",
|
||||||
|
},
|
||||||
|
registrationProvinceName: {
|
||||||
|
propertyName: "registrationProvinceId",
|
||||||
|
joinRelation: "registrationProvince",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
registrationDistrictName: {
|
||||||
|
propertyName: "registrationDistrictId",
|
||||||
|
joinRelation: "registrationDistrict",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
registrationSubDistrictName: {
|
||||||
|
propertyName: "registrationSubDistrictId",
|
||||||
|
joinRelation: "registrationSubDistrict",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
currentProvinceName: {
|
||||||
|
propertyName: "currentProvinceId",
|
||||||
|
joinRelation: "currentProvince",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
currentDistrictName: {
|
||||||
|
propertyName: "currentDistrictId",
|
||||||
|
joinRelation: "currentDistrict",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
currentSubDistrictName: {
|
||||||
|
propertyName: "currentSubDistrictId",
|
||||||
|
joinRelation: "currentSubDistrict",
|
||||||
|
joinField: "name",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity
|
||||||
|
private readonly POSITION_FIELD_REPLACEMENTS: Record<
|
||||||
|
string,
|
||||||
|
{ propertyName: string; joinRelation: string; joinField: string }
|
||||||
|
> = {
|
||||||
|
posTypeName: {
|
||||||
|
propertyName: "posTypeId",
|
||||||
|
joinRelation: "posType",
|
||||||
|
joinField: "posTypeName",
|
||||||
|
},
|
||||||
|
posLevelName: {
|
||||||
|
propertyName: "posLevelId",
|
||||||
|
joinRelation: "posLevel",
|
||||||
|
joinField: "posLevelName",
|
||||||
|
},
|
||||||
|
posExecutiveName: {
|
||||||
|
propertyName: "posExecutiveId",
|
||||||
|
joinRelation: "posExecutive",
|
||||||
|
joinField: "posExecutiveName",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build posMaster permission condition
|
||||||
|
* @summary สร้างเงื่อนไขการกรองข้อมูลตามสิทธิ์การเข้าถึง
|
||||||
|
*/
|
||||||
|
private buildPosMasterPermissionCondition(
|
||||||
|
accessType: string | undefined,
|
||||||
|
dnaIds: {
|
||||||
|
dnaRootId?: string | null;
|
||||||
|
dnaChild1Id?: string | null;
|
||||||
|
dnaChild2Id?: string | null;
|
||||||
|
dnaChild3Id?: string | null;
|
||||||
|
dnaChild4Id?: string | null;
|
||||||
|
},
|
||||||
|
tableAlias: string = "posMaster",
|
||||||
|
): string {
|
||||||
|
// ALL - no filtering
|
||||||
|
if (accessType === "ALL") {
|
||||||
|
return "1=1";
|
||||||
|
}
|
||||||
|
|
||||||
|
// No access type specified but has DNA IDs - default to NORMAL behavior
|
||||||
|
const conditions: string[] = [];
|
||||||
|
|
||||||
|
if (accessType === "ROOT" && dnaIds.dnaRootId) {
|
||||||
|
// All organizations under this root
|
||||||
|
conditions.push(
|
||||||
|
`${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%")`,
|
||||||
|
);
|
||||||
|
} else if (accessType === "CHILD" || accessType === "NORMAL") {
|
||||||
|
// Build conditions based on which DNA level is specified
|
||||||
|
if (dnaIds.dnaChild4Id) {
|
||||||
|
conditions.push(
|
||||||
|
`${tableAlias}.orgChild4Id IN (SELECT id FROM orgChild4 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild4Id}")`,
|
||||||
|
);
|
||||||
|
} else if (dnaIds.dnaChild3Id) {
|
||||||
|
conditions.push(
|
||||||
|
`${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild3Id}")`,
|
||||||
|
);
|
||||||
|
// For CHILD type, include all descendants
|
||||||
|
if (accessType === "CHILD") {
|
||||||
|
conditions.push(
|
||||||
|
`(${tableAlias}.orgChild3Id IN (SELECT id FROM orgChild3 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild3Id}%") OR ${tableAlias}.orgChild4Id IS NOT NULL)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (dnaIds.dnaChild2Id) {
|
||||||
|
conditions.push(
|
||||||
|
`${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild2Id}")`,
|
||||||
|
);
|
||||||
|
if (accessType === "CHILD") {
|
||||||
|
conditions.push(
|
||||||
|
`(${tableAlias}.orgChild2Id IN (SELECT id FROM orgChild2 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild2Id}%") OR ${tableAlias}.orgChild3Id IS NOT NULL)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (dnaIds.dnaChild1Id) {
|
||||||
|
conditions.push(
|
||||||
|
`${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaChild1Id}")`,
|
||||||
|
);
|
||||||
|
if (accessType === "CHILD") {
|
||||||
|
conditions.push(
|
||||||
|
`(${tableAlias}.orgChild1Id IN (SELECT id FROM orgChild1 WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaChild1Id}%") OR ${tableAlias}.orgChild2Id IS NOT NULL)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (dnaIds.dnaRootId) {
|
||||||
|
conditions.push(
|
||||||
|
`${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA = "${dnaIds.dnaRootId}")`,
|
||||||
|
);
|
||||||
|
if (accessType === "CHILD") {
|
||||||
|
conditions.push(
|
||||||
|
`(${tableAlias}.orgRootId IN (SELECT id FROM orgRoot WHERE orgRevisionId = "${this.currentRevisionId}" AND ancestorDNA LIKE "${dnaIds.dnaRootId}%") OR ${tableAlias}.orgChild1Id IS NOT NULL)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditions.length > 0 ? `(${conditions.join(" OR ")})` : "1=1";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list fields by systems
|
* list fields by systems
|
||||||
|
|
@ -50,7 +197,7 @@ export class ApiWebServiceController extends Controller {
|
||||||
}
|
}
|
||||||
await isPermissionRequest(request, apiName.id);
|
await isPermissionRequest(request, apiName.id);
|
||||||
const offset = (page - 1) * pageSize;
|
const offset = (page - 1) * pageSize;
|
||||||
const propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`);
|
let propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`);
|
||||||
|
|
||||||
let tbMain: string = "";
|
let tbMain: string = "";
|
||||||
let condition: string = "1=1";
|
let condition: string = "1=1";
|
||||||
|
|
@ -78,6 +225,104 @@ export class ApiWebServiceController extends Controller {
|
||||||
condition = `PosMaster.orgRevisionId = "${revision?.id}"`;
|
condition = `PosMaster.orgRevisionId = "${revision?.id}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let posMasterCondition: string = "";
|
||||||
|
let posMasterAlias: string = "";
|
||||||
|
|
||||||
|
// Special handling for Profile and ProfileEmployee systems with permission filtering
|
||||||
|
if (system == "registry") {
|
||||||
|
// Get current revision
|
||||||
|
const revision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store for use in permission building
|
||||||
|
this.currentRevisionId = revision?.id || "";
|
||||||
|
posMasterAlias = "posMaster";
|
||||||
|
|
||||||
|
// Build permission condition
|
||||||
|
posMasterCondition = this.buildPosMasterPermissionCondition(
|
||||||
|
request.user.accessType,
|
||||||
|
{
|
||||||
|
dnaRootId: request.user.dnaRootId,
|
||||||
|
dnaChild1Id: request.user.dnaChild1Id,
|
||||||
|
dnaChild2Id: request.user.dnaChild2Id,
|
||||||
|
dnaChild3Id: request.user.dnaChild3Id,
|
||||||
|
dnaChild4Id: request.user.dnaChild4Id,
|
||||||
|
},
|
||||||
|
posMasterAlias,
|
||||||
|
);
|
||||||
|
} else if (system == "registry_emp") {
|
||||||
|
// Get current revision
|
||||||
|
const revision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store for use in permission building
|
||||||
|
this.currentRevisionId = revision?.id || "";
|
||||||
|
posMasterAlias = "employeePosMaster";
|
||||||
|
|
||||||
|
// Build permission condition
|
||||||
|
posMasterCondition = this.buildPosMasterPermissionCondition(
|
||||||
|
request.user.accessType,
|
||||||
|
{
|
||||||
|
dnaRootId: request.user.dnaRootId,
|
||||||
|
dnaChild1Id: request.user.dnaChild1Id,
|
||||||
|
dnaChild2Id: request.user.dnaChild2Id,
|
||||||
|
dnaChild3Id: request.user.dnaChild3Id,
|
||||||
|
dnaChild4Id: request.user.dnaChild4Id,
|
||||||
|
},
|
||||||
|
posMasterAlias,
|
||||||
|
);
|
||||||
|
} else if (system == "registry_temp") {
|
||||||
|
// Get current revision
|
||||||
|
const revision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store for use in permission building
|
||||||
|
this.currentRevisionId = revision?.id || "";
|
||||||
|
posMasterAlias = "employeeTempPosMaster";
|
||||||
|
|
||||||
|
// Build permission condition
|
||||||
|
posMasterCondition = this.buildPosMasterPermissionCondition(
|
||||||
|
request.user.accessType,
|
||||||
|
{
|
||||||
|
dnaRootId: request.user.dnaRootId,
|
||||||
|
dnaChild1Id: request.user.dnaChild1Id,
|
||||||
|
dnaChild2Id: request.user.dnaChild2Id,
|
||||||
|
dnaChild3Id: request.user.dnaChild3Id,
|
||||||
|
dnaChild4Id: request.user.dnaChild4Id,
|
||||||
|
},
|
||||||
|
posMasterAlias,
|
||||||
|
);
|
||||||
|
} else if (system == "position") {
|
||||||
|
// Get current revision
|
||||||
|
const revision = await this.orgRevisionRepository.findOne({
|
||||||
|
select: ["id"],
|
||||||
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store for use in permission building
|
||||||
|
this.currentRevisionId = revision?.id || "";
|
||||||
|
posMasterAlias = "PosMaster"; // Note: Uses PascalCase to match tbMain alias
|
||||||
|
|
||||||
|
// Build permission condition
|
||||||
|
posMasterCondition = this.buildPosMasterPermissionCondition(
|
||||||
|
request.user.accessType,
|
||||||
|
{
|
||||||
|
dnaRootId: request.user.dnaRootId,
|
||||||
|
dnaChild1Id: request.user.dnaChild1Id,
|
||||||
|
dnaChild2Id: request.user.dnaChild2Id,
|
||||||
|
dnaChild3Id: request.user.dnaChild3Id,
|
||||||
|
dnaChild4Id: request.user.dnaChild4Id,
|
||||||
|
},
|
||||||
|
posMasterAlias,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const repo = AppDataSource.getRepository(tbMain);
|
const repo = AppDataSource.getRepository(tbMain);
|
||||||
const metadata = repo.metadata;
|
const metadata = repo.metadata;
|
||||||
|
|
||||||
|
|
@ -92,6 +337,40 @@ export class ApiWebServiceController extends Controller {
|
||||||
...new Set(propertyKey.map((x) => x.split(".")[0]).filter((tb) => tb !== tbMain)),
|
...new Set(propertyKey.map((x) => x.split(".")[0]).filter((tb) => tb !== tbMain)),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// สำหรับ Profile: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey
|
||||||
|
const profileFieldJoins: Record<string, string> = {}; // alias -> relationName
|
||||||
|
if (tbMain === "Profile") {
|
||||||
|
propertyKey = propertyKey.map((key) => {
|
||||||
|
const [table, field] = key.split(".");
|
||||||
|
if (table === "Profile") {
|
||||||
|
const replacement = this.PROFILE_FIELD_REPLACEMENTS[field];
|
||||||
|
if (replacement) {
|
||||||
|
const alias = `${table}_${replacement.joinRelation}`;
|
||||||
|
profileFieldJoins[alias] = replacement.joinRelation;
|
||||||
|
return `${alias}.${replacement.joinField}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// สำหรับ Position: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey
|
||||||
|
const positionFieldJoins: Record<string, string> = {}; // alias -> relationName
|
||||||
|
if (tbMain === "Position") {
|
||||||
|
propertyKey = propertyKey.map((key) => {
|
||||||
|
const [table, field] = key.split(".");
|
||||||
|
if (table === "Position") {
|
||||||
|
const replacement = this.POSITION_FIELD_REPLACEMENTS[field];
|
||||||
|
if (replacement) {
|
||||||
|
const alias = `${table}_${replacement.joinRelation}`;
|
||||||
|
positionFieldJoins[alias] = replacement.joinRelation;
|
||||||
|
return `${alias}.${replacement.joinField}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const queryBuilder = repo.createQueryBuilder(tbMain);
|
const queryBuilder = repo.createQueryBuilder(tbMain);
|
||||||
|
|
||||||
// join กับตารารอง
|
// join กับตารารอง
|
||||||
|
|
@ -107,6 +386,49 @@ export class ApiWebServiceController extends Controller {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// join สำหรับฟิลด์ Profile ที่ต้องการดึงค่าจากตารางอื่น
|
||||||
|
if (tbMain === "Profile" && Object.keys(profileFieldJoins).length > 0) {
|
||||||
|
Object.entries(profileFieldJoins).forEach(([alias, relationName]) => {
|
||||||
|
queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// join สำหรับฟิลด์ Position ที่ต้องการดึงค่าจากตารางอื่น
|
||||||
|
if (tbMain === "Position" && Object.keys(positionFieldJoins).length > 0) {
|
||||||
|
Object.entries(positionFieldJoins).forEach(([alias, relationName]) => {
|
||||||
|
queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง)
|
||||||
|
const posMasterProfileFields: string[] = [];
|
||||||
|
if (tbMain === "PosMaster") {
|
||||||
|
propertyKey.forEach((key) => {
|
||||||
|
if (key.startsWith("Profile.")) {
|
||||||
|
posMasterProfileFields.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// join PosMaster กับ Profile เมื่อมีการขอ Profile fields
|
||||||
|
if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) {
|
||||||
|
queryBuilder.leftJoin("PosMaster.current_holder", "Profile");
|
||||||
|
}
|
||||||
|
|
||||||
|
// join กับ posMaster/employeePosMaster/employeeTempPosMaster เพื่อกรองตามสิทธิ์การเข้าถึง
|
||||||
|
if ((tbMain === "Profile" || tbMain === "ProfileEmployee") && posMasterCondition !== "1=1") {
|
||||||
|
if (tbMain === "Profile") {
|
||||||
|
queryBuilder.leftJoin("Profile.current_holders", "posMaster");
|
||||||
|
} else if (tbMain === "ProfileEmployee") {
|
||||||
|
// Use the correct relation based on posMasterAlias
|
||||||
|
if (posMasterAlias === "employeeTempPosMaster") {
|
||||||
|
queryBuilder.leftJoin("ProfileEmployee.current_holderTemps", "employeeTempPosMaster");
|
||||||
|
} else {
|
||||||
|
queryBuilder.leftJoin("ProfileEmployee.current_holders", "employeePosMaster");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน
|
// // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน
|
||||||
// if (!propertyKey.includes(`${Main}.id`)) {
|
// if (!propertyKey.includes(`${Main}.id`)) {
|
||||||
// propertyKey.push(`${Main}.id`);
|
// propertyKey.push(`${Main}.id`);
|
||||||
|
|
@ -125,6 +447,7 @@ export class ApiWebServiceController extends Controller {
|
||||||
const [items, total] = await queryBuilder
|
const [items, total] = await queryBuilder
|
||||||
.select(propertyKey)
|
.select(propertyKey)
|
||||||
.where(condition)
|
.where(condition)
|
||||||
|
.andWhere(posMasterCondition)
|
||||||
.orderBy(propertyKey[0], "ASC")
|
.orderBy(propertyKey[0], "ASC")
|
||||||
.skip(offset)
|
.skip(offset)
|
||||||
.take(pageSize)
|
.take(pageSize)
|
||||||
|
|
@ -141,8 +464,55 @@ export class ApiWebServiceController extends Controller {
|
||||||
|
|
||||||
// split object id ออกก่อน return
|
// split object id ออกก่อน return
|
||||||
const data = items.map((item) => {
|
const data = items.map((item) => {
|
||||||
const { [pk]: removedPk, ...x } = item;
|
const { [pk]: removedPk, ...rest } = item;
|
||||||
return x;
|
|
||||||
|
// สำหรับ Profile: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
|
||||||
|
if (tbMain === "Profile") {
|
||||||
|
const flattened: any = { ...rest };
|
||||||
|
Object.entries(this.PROFILE_FIELD_REPLACEMENTS).forEach(([nameField, config]) => {
|
||||||
|
const alias = `${tbMain}_${config.joinRelation}`;
|
||||||
|
if (rest[alias] && rest[alias][config.joinField] !== undefined) {
|
||||||
|
flattened[nameField] = rest[alias][config.joinField];
|
||||||
|
delete flattened[alias];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return flattened;
|
||||||
|
}
|
||||||
|
|
||||||
|
// สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
|
||||||
|
if (tbMain === "Position") {
|
||||||
|
const flattened: any = { ...rest };
|
||||||
|
Object.entries(this.POSITION_FIELD_REPLACEMENTS).forEach(([nameField, config]) => {
|
||||||
|
// Remove the original ID field
|
||||||
|
delete flattened[config.propertyName];
|
||||||
|
// Add the name field from joined table
|
||||||
|
const alias = `${tbMain}_${config.joinRelation}`;
|
||||||
|
if (rest[alias] && rest[alias][config.joinField] !== undefined) {
|
||||||
|
flattened[nameField] = rest[alias][config.joinField];
|
||||||
|
}
|
||||||
|
// Remove the joined table object
|
||||||
|
delete flattened[alias];
|
||||||
|
});
|
||||||
|
return flattened;
|
||||||
|
}
|
||||||
|
|
||||||
|
// สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน
|
||||||
|
if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) {
|
||||||
|
const flattened: any = { ...rest };
|
||||||
|
// Extract Profile fields and add them at top level with "profile_" prefix to avoid conflicts
|
||||||
|
if (rest["Profile"]) {
|
||||||
|
flattened["profile_prefix"] = rest["Profile"].prefix;
|
||||||
|
flattened["profile_rank"] = rest["Profile"].rank;
|
||||||
|
flattened["profile_firstName"] = rest["Profile"].firstName;
|
||||||
|
flattened["profile_lastName"] = rest["Profile"].lastName;
|
||||||
|
flattened["profile_citizenId"] = rest["Profile"].citizenId;
|
||||||
|
// Remove the nested Profile object
|
||||||
|
delete flattened["Profile"];
|
||||||
|
}
|
||||||
|
return flattened;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rest;
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("queryBuilder ===> ", queryBuilder.getQuery());
|
// console.log("queryBuilder ===> ", queryBuilder.getQuery());
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export class ScriptProfileOrgController extends Controller {
|
||||||
* @summary Update org structure for profiles updated within a certain time window and sync to Keycloak
|
* @summary Update org structure for profiles updated within a certain time window and sync to Keycloak
|
||||||
*/
|
*/
|
||||||
@Post("update-org")
|
@Post("update-org")
|
||||||
public async cronjobUpdateOrg(@Request() request: RequestWithUser) {
|
public async cronjobUpdateOrg(@Request() _request: RequestWithUser) {
|
||||||
// Idempotency check - prevent concurrent runs
|
// Idempotency check - prevent concurrent runs
|
||||||
if (this.isRunning) {
|
if (this.isRunning) {
|
||||||
console.log("cronjobUpdateOrg: Job already running, skipping this execution");
|
console.log("cronjobUpdateOrg: Job already running, skipping this execution");
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,17 @@ export async function handleWebServiceAuth(request: express.Request) {
|
||||||
|
|
||||||
// ตรวจสอบ API Key กับฐานข้อมูล
|
// ตรวจสอบ API Key กับฐานข้อมูล
|
||||||
const apiKeyData = await AppDataSource.getRepository(ApiKey).findOne({
|
const apiKeyData = await AppDataSource.getRepository(ApiKey).findOne({
|
||||||
select: { id: true, name: true, keyApi: true },
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
keyApi: true,
|
||||||
|
accessType: true,
|
||||||
|
dnaRootId: true,
|
||||||
|
dnaChild1Id: true,
|
||||||
|
dnaChild2Id: true,
|
||||||
|
dnaChild3Id: true,
|
||||||
|
dnaChild4Id: true,
|
||||||
|
},
|
||||||
where: { keyApi: apiKey },
|
where: { keyApi: apiKey },
|
||||||
relations: ["apiNames"],
|
relations: ["apiNames"],
|
||||||
});
|
});
|
||||||
|
|
@ -40,6 +50,12 @@ export async function handleWebServiceAuth(request: express.Request) {
|
||||||
name: apiKeyData.name,
|
name: apiKeyData.name,
|
||||||
type: "web-service",
|
type: "web-service",
|
||||||
accessApi: apiKeyData.apiNames.map((x) => x.id) ?? [],
|
accessApi: apiKeyData.apiNames.map((x) => x.id) ?? [],
|
||||||
|
accessType: apiKeyData.accessType,
|
||||||
|
dnaRootId: apiKeyData.dnaRootId,
|
||||||
|
dnaChild1Id: apiKeyData.dnaChild1Id,
|
||||||
|
dnaChild2Id: apiKeyData.dnaChild2Id,
|
||||||
|
dnaChild3Id: apiKeyData.dnaChild3Id,
|
||||||
|
dnaChild4Id: apiKeyData.dnaChild4Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,11 @@ export type RequestWithUserWebService = Request & {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
accessApi: string[];
|
accessApi: string[];
|
||||||
|
accessType?: string;
|
||||||
|
dnaRootId?: string | null;
|
||||||
|
dnaChild1Id?: string | null;
|
||||||
|
dnaChild2Id?: string | null;
|
||||||
|
dnaChild3Id?: string | null;
|
||||||
|
dnaChild4Id?: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -530,18 +530,20 @@ export class KeycloakAttributeService {
|
||||||
// Initialize rate limiter if rate limiting is enabled
|
// Initialize rate limiter if rate limiting is enabled
|
||||||
if (rateLimit && rateLimit > 0) {
|
if (rateLimit && rateLimit > 0) {
|
||||||
rateLimiter = new RateLimiter(rateLimit);
|
rateLimiter = new RateLimiter(rateLimit);
|
||||||
console.log(`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`);
|
console.log(
|
||||||
|
`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select repository based on profile type
|
// Select repository based on profile type
|
||||||
const repo =
|
const repo = profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo;
|
||||||
profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo;
|
|
||||||
|
|
||||||
// Query profiles updated within the month
|
// Query profiles updated within the month
|
||||||
const profiles = await repo
|
const profiles = await repo
|
||||||
.createQueryBuilder("p")
|
.createQueryBuilder("p")
|
||||||
.where("p.keycloak IS NOT NULL")
|
.where("p.keycloak IS NOT NULL")
|
||||||
.andWhere("p.keycloak != :empty", { empty: "" })
|
.andWhere("p.keycloak != :empty", { empty: "" })
|
||||||
|
.andWhere("p.isDelete = :isDelete", { isDelete: false })
|
||||||
.andWhere("p.lastUpdatedAt BETWEEN :start AND :end", {
|
.andWhere("p.lastUpdatedAt BETWEEN :start AND :end", {
|
||||||
start: startDate,
|
start: startDate,
|
||||||
end: endDate,
|
end: endDate,
|
||||||
|
|
@ -579,8 +581,7 @@ export class KeycloakAttributeService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if empType is empty in Keycloak
|
// Check if empType is empty in Keycloak
|
||||||
const { isEmpty, currentEmpType } =
|
const { isEmpty, currentEmpType } = await this.checkEmpTypeEmpty(keycloakUserId);
|
||||||
await this.checkEmpTypeEmpty(keycloakUserId);
|
|
||||||
|
|
||||||
result.profilesChecked++;
|
result.profilesChecked++;
|
||||||
|
|
||||||
|
|
@ -607,8 +608,7 @@ export class KeycloakAttributeService {
|
||||||
|
|
||||||
// Sync the profile
|
// Sync the profile
|
||||||
const success = await withRetry(
|
const success = await withRetry(
|
||||||
async () =>
|
async () => this.syncOnOrganizationChange(profile.id, profileType),
|
||||||
this.syncOnOrganizationChange(profile.id, profileType),
|
|
||||||
3, // maxRetries
|
3, // maxRetries
|
||||||
1000, // baseDelay
|
1000, // baseDelay
|
||||||
);
|
);
|
||||||
|
|
@ -768,7 +768,13 @@ export class KeycloakAttributeService {
|
||||||
maxRetries?: number; // Retry attempts for failed operations
|
maxRetries?: number; // Retry attempts for failed operations
|
||||||
rateLimit?: number; // Requests per second
|
rateLimit?: number; // Requests per second
|
||||||
clearProgress?: boolean; // Start fresh, ignore existing progress
|
clearProgress?: boolean; // Start fresh, ignore existing progress
|
||||||
}): Promise<{ total: number; success: number; failed: number; details: any[]; resumed?: boolean }> {
|
}): Promise<{
|
||||||
|
total: number;
|
||||||
|
success: number;
|
||||||
|
failed: number;
|
||||||
|
details: any[];
|
||||||
|
resumed?: boolean;
|
||||||
|
}> {
|
||||||
const limit = options?.limit;
|
const limit = options?.limit;
|
||||||
const concurrency = options?.concurrency ?? 5;
|
const concurrency = options?.concurrency ?? 5;
|
||||||
const resume = options?.resume ?? false;
|
const resume = options?.resume ?? false;
|
||||||
|
|
@ -922,7 +928,10 @@ export class KeycloakAttributeService {
|
||||||
// Save progress after each batch
|
// Save progress after each batch
|
||||||
SyncProgressManager.save(updatedState);
|
SyncProgressManager.save(updatedState);
|
||||||
// Log progress every 50 items
|
// Log progress every 50 items
|
||||||
if (updatedState.lastSyncedIndex % 50 === 0 || updatedState.lastSyncedIndex === updatedState.totalProfiles) {
|
if (
|
||||||
|
updatedState.lastSyncedIndex % 50 === 0 ||
|
||||||
|
updatedState.lastSyncedIndex === updatedState.totalProfiles
|
||||||
|
) {
|
||||||
SyncProgressManager.logProgress(updatedState);
|
SyncProgressManager.logProgress(updatedState);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue