949 lines
37 KiB
TypeScript
949 lines
37 KiB
TypeScript
import { Controller, Route, Security, Tags, Path, Request, Response, Get, Query } from "tsoa";
|
|
import { AppDataSource } from "../database/data-source";
|
|
import HttpSuccess from "../interfaces/http-success";
|
|
import HttpStatusCode from "../interfaces/http-status";
|
|
import HttpError from "../interfaces/http-error";
|
|
import { ApiName } from "../entities/ApiName";
|
|
import { isPermissionRequest } from "../middlewares/authWebService";
|
|
import { RequestWithUserWebService } from "../middlewares/user";
|
|
import { OrgRevision } from "../entities/OrgRevision";
|
|
import { ApiHistory } from "../entities/ApiHistory";
|
|
import { OrgChild1 } from "../entities/OrgChild1";
|
|
import { OrgChild2 } from "../entities/OrgChild2";
|
|
import { OrgChild3 } from "../entities/OrgChild3";
|
|
import { OrgChild4 } from "../entities/OrgChild4";
|
|
import { SystemCode } from "./../interfaces/api-type";
|
|
@Route("api/v1/org/api-service")
|
|
@Tags("ApiKey")
|
|
@Security("webServiceAuth")
|
|
@Response(
|
|
HttpStatusCode.INTERNAL_SERVER_ERROR,
|
|
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
|
|
)
|
|
export class ApiWebServiceController extends Controller {
|
|
private apiNameRepository = AppDataSource.getRepository(ApiName);
|
|
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
|
|
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",
|
|
},
|
|
};
|
|
|
|
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileEmployee entity
|
|
private readonly PROFILEEMPLOYEE_FIELD_REPLACEMENTS: Record<
|
|
string,
|
|
{ propertyName: string; joinRelation: string; joinField: string }
|
|
> = {
|
|
posTypeName: {
|
|
propertyName: "posTypeId",
|
|
joinRelation: "posType",
|
|
joinField: "posTypeName",
|
|
},
|
|
posLevelName: {
|
|
propertyName: "posLevelId",
|
|
joinRelation: "posLevel",
|
|
joinField: "posLevelName",
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 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";
|
|
}
|
|
|
|
/**
|
|
* rename ancestorDNA to id
|
|
* @summary เปลี่ยนชื่อฟิลด์ ancestorDNA เป็น id
|
|
*/
|
|
private renameAncestorDnaToId(obj: any): any {
|
|
if (!obj || typeof obj !== "object") {
|
|
return obj;
|
|
}
|
|
const result = { ...obj };
|
|
if (result.ancestorDNA !== undefined) {
|
|
result.id = result.ancestorDNA;
|
|
delete result.ancestorDNA;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* list fields by systems
|
|
* @summary รายการ fields ตาม systems
|
|
*/
|
|
@Get("/:system/:code")
|
|
async listAttribute(
|
|
@Request() request: RequestWithUserWebService,
|
|
@Path("system")
|
|
system: SystemCode,
|
|
@Path("code") code: string,
|
|
@Query("page") page: number = 1,
|
|
@Query("pageSize") pageSize: number = 100,
|
|
): Promise<HttpSuccess | HttpError> {
|
|
const apiName = await this.apiNameRepository.findOne({
|
|
where: { code },
|
|
select: ["id", "code", "methodApi", "system", "isActive"],
|
|
relations: ["apiAttributes"],
|
|
order: {
|
|
apiAttributes: {
|
|
ordering: "ASC",
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!apiName || apiName.system != system || !apiName.isActive || apiName.methodApi != "GET") {
|
|
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบ API ที่ระบุ");
|
|
}
|
|
await isPermissionRequest(request, apiName.id);
|
|
const offset = (page - 1) * pageSize;
|
|
let propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`);
|
|
const selectedFieldsByTable: Record<string, Set<string>> = {};
|
|
apiName.apiAttributes.forEach((attr) => {
|
|
if (!selectedFieldsByTable[attr.tbName]) {
|
|
selectedFieldsByTable[attr.tbName] = new Set<string>();
|
|
}
|
|
selectedFieldsByTable[attr.tbName].add(attr.propertyKey);
|
|
});
|
|
|
|
// สำหรับ Organization: ให้รวม ancestorDNA เสมอ เพื่อแสดงเป็น id
|
|
if (system === "organization") {
|
|
// สำหรับ OrgRoot
|
|
const ancestorDnaField = "OrgRoot.ancestorDNA";
|
|
if (!propertyKey.includes(ancestorDnaField)) {
|
|
propertyKey.push(ancestorDnaField);
|
|
}
|
|
if (!selectedFieldsByTable["OrgRoot"]) {
|
|
selectedFieldsByTable["OrgRoot"] = new Set<string>();
|
|
}
|
|
selectedFieldsByTable["OrgRoot"].add("ancestorDNA");
|
|
|
|
// สำหรับ OrgChild1, OrgChild2, OrgChild3, OrgChild4
|
|
const childTables = ["OrgChild1", "OrgChild2", "OrgChild3", "OrgChild4"];
|
|
childTables.forEach((table) => {
|
|
if (!selectedFieldsByTable[table]) {
|
|
selectedFieldsByTable[table] = new Set<string>();
|
|
}
|
|
selectedFieldsByTable[table].add("ancestorDNA");
|
|
});
|
|
}
|
|
|
|
let tbMain: string = "";
|
|
let condition: string = "1=1";
|
|
if (system == "registry") {
|
|
tbMain = "Profile";
|
|
} else if (system == "registry_emp") {
|
|
tbMain = "ProfileEmployee";
|
|
condition = `ProfileEmployee.employeeClass = "PERM"`;
|
|
} else if (system == "registry_temp") {
|
|
tbMain = "ProfileEmployee";
|
|
condition = `ProfileEmployee.employeeClass = "TEMP"`;
|
|
} else if (system == "organization") {
|
|
tbMain = "OrgRoot";
|
|
const revision = await this.orgRevisionRepository.findOne({
|
|
select: ["id"],
|
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
|
});
|
|
condition = `OrgRoot.orgRevisionId = "${revision?.id}"`;
|
|
} else if (system == "position") {
|
|
tbMain = "PosMaster";
|
|
const revision = await this.orgRevisionRepository.findOne({
|
|
select: ["id"],
|
|
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
|
|
});
|
|
condition = `PosMaster.orgRevisionId = "${revision?.id}"`;
|
|
}
|
|
|
|
let posMasterCondition: string = "1=1";
|
|
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 metadata = repo.metadata;
|
|
|
|
const relationMap: Record<string, string> = {};
|
|
metadata.relations.forEach((rel) => {
|
|
relationMap[rel.inverseEntityMetadata.name] = rel.propertyName;
|
|
});
|
|
|
|
// ดึงเฉพาะตารางรอง (ถ้าเลือกไว้)
|
|
let propertyOtherKey: any[] = [];
|
|
propertyOtherKey = [
|
|
...new Set(propertyKey.map((x) => x.split(".")[0]).filter((tb) => tb !== tbMain)),
|
|
];
|
|
|
|
// Organization hierarchy is assembled in a separate step; keep main query focused on OrgRoot only.
|
|
if (tbMain === "OrgRoot") {
|
|
const orgChildTables = new Set(["OrgChild1", "OrgChild2", "OrgChild3", "OrgChild4"]);
|
|
propertyKey = propertyKey.filter((key) => !orgChildTables.has(key.split(".")[0]));
|
|
propertyOtherKey = propertyOtherKey.filter((tb) => !orgChildTables.has(tb));
|
|
}
|
|
|
|
// สำหรับ 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" || tbMain === "PosMaster") {
|
|
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;
|
|
});
|
|
}
|
|
|
|
// สำหรับ ProfileEmployee: ตรวจสอบฟิลด์ที่ต้องการ join และแปลง propertyKey
|
|
const profileEmployeeFieldJoins: Record<string, string> = {}; // alias -> relationName
|
|
if (tbMain === "ProfileEmployee") {
|
|
propertyKey = propertyKey.map((key) => {
|
|
const [table, field] = key.split(".");
|
|
if (table === "ProfileEmployee") {
|
|
const replacement = this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[field];
|
|
if (replacement) {
|
|
const alias = `${table}_${replacement.joinRelation}`;
|
|
profileEmployeeFieldJoins[alias] = replacement.joinRelation;
|
|
return `${alias}.${replacement.joinField}`;
|
|
}
|
|
}
|
|
return key;
|
|
});
|
|
}
|
|
|
|
const queryBuilder = repo.createQueryBuilder(tbMain);
|
|
|
|
// join กับตารารอง
|
|
if (propertyOtherKey.length > 0) {
|
|
propertyOtherKey.forEach((tb) => {
|
|
// Skip Profile join for PosMaster - it's handled separately below
|
|
if (tbMain === "PosMaster" && tb === "Profile") {
|
|
return;
|
|
}
|
|
|
|
// Skip Position join for PosMaster - it's handled separately below
|
|
if (tbMain === "PosMaster" && tb === "Position") {
|
|
return;
|
|
}
|
|
|
|
const relationName = relationMap[tb];
|
|
if (relationName) {
|
|
queryBuilder.leftJoin(`${tbMain}.${relationName}`, tb);
|
|
} else {
|
|
// Remove fields from this table from propertyKey
|
|
propertyKey = propertyKey.filter((key) => !key.startsWith(`${tb}.`));
|
|
}
|
|
});
|
|
}
|
|
|
|
// Check if propertyKey is empty after filtering
|
|
if (propertyKey.length === 0) {
|
|
throw new HttpError(
|
|
HttpStatusCode.BAD_REQUEST,
|
|
"ไม่พบฟิลด์ที่ต้องการแสดงผล กรุณาตรวจสอบการตั้งค่า API (ไม่สามารถ join ตารางลูกได้)",
|
|
);
|
|
}
|
|
|
|
// 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" || tbMain === "PosMaster") &&
|
|
Object.keys(positionFieldJoins).length > 0
|
|
) {
|
|
if (tbMain === "PosMaster") {
|
|
const posMasterPositionRelation = relationMap["Position"];
|
|
if (!posMasterPositionRelation) {
|
|
throw new HttpError(
|
|
HttpStatusCode.BAD_REQUEST,
|
|
"ไม่พบความสัมพันธ์ระหว่าง PosMaster กับ Position กรุณาตรวจสอบการตั้งค่า API",
|
|
);
|
|
}
|
|
|
|
// Join PosMaster -> Position once using actual relation name from metadata
|
|
queryBuilder.leftJoin(`PosMaster.${posMasterPositionRelation}`, "Position");
|
|
}
|
|
|
|
Object.entries(positionFieldJoins).forEach(([alias, relationName]) => {
|
|
if (tbMain === "PosMaster") {
|
|
queryBuilder.leftJoin(`Position.${relationName}`, alias);
|
|
} else {
|
|
queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias);
|
|
}
|
|
});
|
|
}
|
|
|
|
// join สำหรับฟิลด์ ProfileEmployee ที่ต้องการดึงค่าจากตารางอื่น
|
|
if (tbMain === "ProfileEmployee" && Object.keys(profileEmployeeFieldJoins).length > 0) {
|
|
Object.entries(profileEmployeeFieldJoins).forEach(([alias, relationName]) => {
|
|
queryBuilder.leftJoin(`${tbMain}.${relationName}`, alias);
|
|
});
|
|
}
|
|
|
|
// join สำหรับ PosMaster เมื่อต้องการดึงค่าจาก Profile (ข้อมูลคนครอง)
|
|
const posMasterProfileFields: string[] = [];
|
|
if (tbMain === "PosMaster") {
|
|
// Collect Profile fields from both formats: "Profile.xxx" and "PosMaster.Profile.xxx"
|
|
const extractedProfileFields = propertyKey
|
|
.filter((key) => key.startsWith("Profile.") || key.startsWith("PosMaster.Profile."))
|
|
.map((key) => key.replace(/^PosMaster\.Profile\./, "Profile."));
|
|
|
|
posMasterProfileFields.push(...new Set(extractedProfileFields));
|
|
|
|
// Remove Profile fields then add back normalized "Profile.xxx" form
|
|
propertyKey = propertyKey.filter(
|
|
(key) => !key.startsWith("Profile.") && !key.startsWith("PosMaster.Profile."),
|
|
);
|
|
propertyKey.push(...posMasterProfileFields);
|
|
}
|
|
|
|
// join PosMaster กับ Profile เมื่อมีการขอ Profile fields
|
|
if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) {
|
|
// Always join via current_holder (not next_holder) because PosMaster has two relations
|
|
// to Profile and relationMap["Profile"] would resolve to next_holder (last defined in entity)
|
|
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 ในการแมบและนับจำนวน
|
|
// if (!propertyKey.includes(`${Main}.id`)) {
|
|
// propertyKey.push(`${Main}.id`);
|
|
// }
|
|
|
|
// add PK - ensure propertyKey is never empty
|
|
let pk: string = "";
|
|
const primaryColumns = metadata.primaryColumns;
|
|
primaryColumns.forEach((col) => {
|
|
pk = col.propertyName;
|
|
if (!propertyKey.includes(`${tbMain}.${pk}`)) {
|
|
propertyKey.push(`${tbMain}.${pk}`);
|
|
}
|
|
});
|
|
|
|
let items: any[] = [];
|
|
let total = 0;
|
|
|
|
if (tbMain === "OrgRoot") {
|
|
// Organization API should always return full hierarchy regardless of page/pageSize.
|
|
[items, total] = await queryBuilder
|
|
.select(propertyKey)
|
|
.where(condition)
|
|
.andWhere(posMasterCondition)
|
|
.orderBy(propertyKey[0] || `${tbMain}.${pk}`, "ASC")
|
|
.getManyAndCount();
|
|
} else {
|
|
[items, total] = await queryBuilder
|
|
.select(propertyKey)
|
|
.where(condition)
|
|
.andWhere(posMasterCondition)
|
|
.orderBy(propertyKey[0] || `${tbMain}.${pk}`, "ASC")
|
|
.skip(offset)
|
|
.take(pageSize)
|
|
.getManyAndCount();
|
|
}
|
|
|
|
// ลบ Main.id
|
|
// const results = items.map(({ id, ...x }) => x);
|
|
// const results = items.map(({ pk, ...x }) => x);
|
|
|
|
// const results = items.map(item => {
|
|
// primaryColumns.forEach(col => delete item[col.propertyName]);
|
|
// return item;
|
|
// });
|
|
|
|
// split object id ออกก่อน return
|
|
const data = items.map((item) => {
|
|
const { [pk]: removedPk, ...rest } = item;
|
|
|
|
// สำหรับ 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];
|
|
}
|
|
// Also handle nested relation objects (e.g., "posLevel": { "posLevelName": "..." })
|
|
if (
|
|
rest[config.joinRelation] &&
|
|
rest[config.joinRelation][config.joinField] !== undefined
|
|
) {
|
|
flattened[nameField] = rest[config.joinRelation][config.joinField];
|
|
delete flattened[config.joinRelation];
|
|
}
|
|
});
|
|
return flattened;
|
|
}
|
|
|
|
// สำหรับ Position: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
|
|
if (tbMain === "Position" || tbMain === "PosMaster") {
|
|
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 = `Position_${config.joinRelation}`;
|
|
if (rest[alias] && rest[alias][config.joinField] !== undefined) {
|
|
flattened[nameField] = rest[alias][config.joinField];
|
|
}
|
|
// Remove the joined table object
|
|
delete flattened[alias];
|
|
});
|
|
// Remove Position object if exists
|
|
if (flattened["Position"]) {
|
|
delete flattened["Position"];
|
|
}
|
|
return flattened;
|
|
}
|
|
|
|
// สำหรับ ProfileEmployee: แปลงฟิลด์ที่มาจาก join กลับเป็นชื่อเดิม
|
|
if (tbMain === "ProfileEmployee") {
|
|
const flattened: any = { ...rest };
|
|
Object.entries(this.PROFILEEMPLOYEE_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];
|
|
// Also handle nested relation objects (e.g., "posType": { "posTypeName": "..." })
|
|
if (
|
|
rest[config.joinRelation] &&
|
|
rest[config.joinRelation][config.joinField] !== undefined
|
|
) {
|
|
flattened[nameField] = rest[config.joinRelation][config.joinField];
|
|
delete flattened[config.joinRelation];
|
|
}
|
|
});
|
|
return flattened;
|
|
}
|
|
|
|
// สำหรับ PosMaster: แปลงฟิลด์ Profile ที่มาจาก join กลับเป็นฟิลด์ระดับบน
|
|
if (tbMain === "PosMaster" && posMasterProfileFields.length > 0) {
|
|
const flattened: any = { ...rest };
|
|
const profileFieldNames = posMasterProfileFields
|
|
.filter((field) => field.startsWith("Profile."))
|
|
.map((field) => field.replace("Profile.", ""));
|
|
|
|
// Extract only requested Profile fields and add top-level aliases
|
|
if (rest["Profile"]) {
|
|
profileFieldNames.forEach((fieldName) => {
|
|
if (rest["Profile"][fieldName] !== undefined) {
|
|
flattened[`profile_${fieldName}`] = rest["Profile"][fieldName];
|
|
}
|
|
});
|
|
// Remove the nested Profile object
|
|
delete flattened["Profile"];
|
|
}
|
|
return flattened;
|
|
}
|
|
|
|
// สำหรับ OrgRoot: เก็บ primary key ไว้ใช้ group ข้อมูล แล้วแยก children ภายหลัง
|
|
if (tbMain === "OrgRoot") {
|
|
return { __rootPk: removedPk, ...rest };
|
|
}
|
|
|
|
return rest;
|
|
});
|
|
|
|
let responseData: any[] = data;
|
|
let responseTotal = total;
|
|
|
|
// สำหรับ Organization: รวมข้อมูลให้เหลือ 1 root ต่อ 1 object และจัด children ตาม hierarchy
|
|
if (tbMain === "OrgRoot") {
|
|
const rootVisibleFields = Array.from(selectedFieldsByTable["OrgRoot"] || []);
|
|
const child1VisibleFields = Array.from(selectedFieldsByTable["OrgChild1"] || []);
|
|
const child2VisibleFields = Array.from(selectedFieldsByTable["OrgChild2"] || []);
|
|
const child3VisibleFields = Array.from(selectedFieldsByTable["OrgChild3"] || []);
|
|
const child4VisibleFields = Array.from(selectedFieldsByTable["OrgChild4"] || []);
|
|
|
|
const pickVisibleFields = (obj: any, fields: string[]) => {
|
|
const out: any = {};
|
|
fields.forEach((field) => {
|
|
if (obj[field] !== undefined) {
|
|
// ถ้าเป็น ancestorDNA ให้เปลี่ยนชื่อเป็น id
|
|
if (field === "ancestorDNA") {
|
|
out.id = obj[field];
|
|
} else {
|
|
out[field] = obj[field];
|
|
}
|
|
}
|
|
});
|
|
return out;
|
|
};
|
|
|
|
const rootMap = new Map<string, any>();
|
|
data.forEach((row: any) => {
|
|
if (!row.__rootPk || rootMap.has(row.__rootPk)) {
|
|
return;
|
|
}
|
|
|
|
const rootNode = {
|
|
...pickVisibleFields(row, rootVisibleFields),
|
|
children: [],
|
|
};
|
|
rootMap.set(row.__rootPk, rootNode);
|
|
});
|
|
|
|
const rootIds = Array.from(rootMap.keys());
|
|
|
|
if (rootIds.length > 0) {
|
|
const buildSelect = (alias: string, required: string[], visible: string[]) =>
|
|
Array.from(new Set([...required, ...visible])).map((field) => `${alias}.${field}`);
|
|
|
|
const [child1Rows, child2Rows, child3Rows, child4Rows] = await Promise.all([
|
|
AppDataSource.getRepository(OrgChild1)
|
|
.createQueryBuilder("OrgChild1")
|
|
.select(buildSelect("OrgChild1", ["id", "orgRootId", "ancestorDNA"], child1VisibleFields))
|
|
.where("OrgChild1.orgRootId IN (:...rootIds)", { rootIds })
|
|
.orderBy("OrgChild1.id", "ASC")
|
|
.getMany(),
|
|
AppDataSource.getRepository(OrgChild2)
|
|
.createQueryBuilder("OrgChild2")
|
|
.select(
|
|
buildSelect("OrgChild2", ["id", "orgRootId", "orgChild1Id", "ancestorDNA"], child2VisibleFields),
|
|
)
|
|
.where("OrgChild2.orgRootId IN (:...rootIds)", { rootIds })
|
|
.orderBy("OrgChild2.id", "ASC")
|
|
.getMany(),
|
|
AppDataSource.getRepository(OrgChild3)
|
|
.createQueryBuilder("OrgChild3")
|
|
.select(
|
|
buildSelect(
|
|
"OrgChild3",
|
|
["id", "orgRootId", "orgChild1Id", "orgChild2Id", "ancestorDNA"],
|
|
child3VisibleFields,
|
|
),
|
|
)
|
|
.where("OrgChild3.orgRootId IN (:...rootIds)", { rootIds })
|
|
.orderBy("OrgChild3.id", "ASC")
|
|
.getMany(),
|
|
AppDataSource.getRepository(OrgChild4)
|
|
.createQueryBuilder("OrgChild4")
|
|
.select(
|
|
buildSelect(
|
|
"OrgChild4",
|
|
["id", "orgRootId", "orgChild1Id", "orgChild2Id", "orgChild3Id", "ancestorDNA"],
|
|
child4VisibleFields,
|
|
),
|
|
)
|
|
.where("OrgChild4.orgRootId IN (:...rootIds)", { rootIds })
|
|
.orderBy("OrgChild4.id", "ASC")
|
|
.getMany(),
|
|
]);
|
|
|
|
const child1Map = new Map<string, any>();
|
|
const child2Map = new Map<string, any>();
|
|
const child3Map = new Map<string, any>();
|
|
|
|
child1Rows.forEach((row) => {
|
|
const node = {
|
|
...pickVisibleFields(row, child1VisibleFields),
|
|
children: [],
|
|
};
|
|
child1Map.set(row.id, node);
|
|
|
|
const rootNode = rootMap.get(row.orgRootId);
|
|
if (rootNode) {
|
|
rootNode.children.push(node);
|
|
}
|
|
});
|
|
|
|
child2Rows.forEach((row) => {
|
|
const node = {
|
|
...pickVisibleFields(row, child2VisibleFields),
|
|
children: [],
|
|
};
|
|
child2Map.set(row.id, node);
|
|
|
|
const parent = child1Map.get(row.orgChild1Id);
|
|
if (parent) {
|
|
parent.children.push(node);
|
|
}
|
|
});
|
|
|
|
child3Rows.forEach((row) => {
|
|
const node = {
|
|
...pickVisibleFields(row, child3VisibleFields),
|
|
children: [],
|
|
};
|
|
child3Map.set(row.id, node);
|
|
|
|
const parent = child2Map.get(row.orgChild2Id);
|
|
if (parent) {
|
|
parent.children.push(node);
|
|
}
|
|
});
|
|
|
|
child4Rows.forEach((row) => {
|
|
const node = {
|
|
...pickVisibleFields(row, child4VisibleFields),
|
|
};
|
|
|
|
const parent = child3Map.get(row.orgChild3Id);
|
|
if (parent) {
|
|
if (!Array.isArray(parent.children)) {
|
|
parent.children = [];
|
|
}
|
|
parent.children.push(node);
|
|
}
|
|
});
|
|
}
|
|
|
|
responseData = Array.from(rootMap.values());
|
|
|
|
// สำหรับ Organization: เปลี่ยนชื่อ ancestorDNA เป็น id
|
|
responseData = responseData.map((root: any) => {
|
|
const renamed = this.renameAncestorDnaToId(root);
|
|
if (Array.isArray(renamed.children)) {
|
|
renamed.children = renamed.children.map((child1: any) => {
|
|
const renamedChild1 = this.renameAncestorDnaToId(child1);
|
|
if (Array.isArray(renamedChild1.children)) {
|
|
renamedChild1.children = renamedChild1.children.map((child2: any) => {
|
|
const renamedChild2 = this.renameAncestorDnaToId(child2);
|
|
if (Array.isArray(renamedChild2.children)) {
|
|
renamedChild2.children = renamedChild2.children.map((child3: any) => {
|
|
const renamedChild3 = this.renameAncestorDnaToId(child3);
|
|
if (Array.isArray(renamedChild3.children)) {
|
|
renamedChild3.children = renamedChild3.children.map((child4: any) =>
|
|
this.renameAncestorDnaToId(child4),
|
|
);
|
|
}
|
|
return renamedChild3;
|
|
});
|
|
}
|
|
return renamedChild2;
|
|
});
|
|
}
|
|
return renamedChild1;
|
|
});
|
|
}
|
|
return renamed;
|
|
});
|
|
|
|
responseTotal = responseData.length;
|
|
}
|
|
|
|
// save api history after query success
|
|
const history = {
|
|
headerApi: JSON.stringify({
|
|
host: request.headers.host,
|
|
"x-api-key": request.headers["x-api-key"],
|
|
connection: request.headers.connection,
|
|
accept: request.headers.accept,
|
|
}),
|
|
tokenApi: Array.isArray(request.headers["x-api-key"])
|
|
? request.headers["x-api-key"][0] || ""
|
|
: request.headers["x-api-key"] || "",
|
|
requestApi: `${request.method} ${request.protocol}://${request.headers.host}${request.originalUrl || request.url}`,
|
|
responseApi: "OK",
|
|
ipApi: request.ip,
|
|
codeApi: code,
|
|
apiKeyId: request.user.id,
|
|
apiNameId: apiName.id,
|
|
createdFullName: request.user.name,
|
|
lastUpdateFullName: request.user.name,
|
|
};
|
|
await this.apiHistoryRepository.save(history);
|
|
|
|
// const results = data.map((item) => {
|
|
// const flattenedItem: any = {};
|
|
|
|
// // Extract nested object properties to top level
|
|
// Object.keys(item).forEach((key) => {
|
|
// const value = item[key];
|
|
// if (value && typeof value === "object") {
|
|
// // if (Array.isArray(value) && value.length === 1) {
|
|
// // // If array has single item, extract it as object
|
|
// // Object.assign(flattenedItem, value[0]);
|
|
// // } else
|
|
// if (!Array.isArray(value)) {
|
|
// // Merge nested object properties to top level
|
|
// Object.assign(flattenedItem, value);
|
|
// } else {
|
|
// // Keep arrays with multiple items or empty arrays as is
|
|
// flattenedItem[key] = value;
|
|
// }
|
|
// } else {
|
|
// // Keep primitive values as is
|
|
// flattenedItem[key] = value;
|
|
// }
|
|
// });
|
|
|
|
// return flattenedItem;
|
|
// });
|
|
return new HttpSuccess({ data: responseData, total: responseTotal });
|
|
}
|
|
}
|