Merge branch 'develop' into adiDev

# Conflicts:
#	src/controllers/PositionController.ts
This commit is contained in:
AdisakKanthawilang 2025-08-15 11:40:46 +07:00
commit 59a0d01f98
6 changed files with 721 additions and 530 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,15 @@
import {
Controller,
Post,
Route,
Security,
Tags,
Body,
Path,
Request,
Response,
Get,
Query,
} from "tsoa";
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 { Profile } from "../entities/Profile";
import { isPermissionRequest } from "../middlewares/authWebService";
import { RequestWithUserWebService } from "../middlewares/user";
@Route("api/v2/org/api-service")
import { OrgRevision } from "../entities/OrgRevision";
import { ApiHistory } from "../entities/ApiHistory";
import { SystemCode } from "./../interfaces/api-type";
@Route("api/v1/org/api-service")
@Tags("ApiKey")
@Security("webServiceAuth")
@Response(
@ -28,6 +18,8 @@ import { RequestWithUserWebService } from "../middlewares/user";
)
export class ApiWebServiceController extends Controller {
private apiNameRepository = AppDataSource.getRepository(ApiName);
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
private apiHistoryRepository = AppDataSource.getRepository(ApiHistory);
/**
* list fields by systems
@ -36,47 +28,173 @@ export class ApiWebServiceController extends Controller {
@Get("/:system/:code")
async listAttribute(
@Request() request: RequestWithUserWebService,
@Path("system") system: "registry" | "registry_emp" | "registry_temp" | "organization",
@Path("system")
system: SystemCode,
@Path("code") code: string,
@Query("page") page: number = 1,
@Query("pageSize") pageSize: number = 100,
): Promise<HttpSuccess | HttpError> {
// try {
const apiName = await this.apiNameRepository.findOne({
where: { code },
select: ["id", "code", "methodApi", "system", "isActive"],
relations: ["apiAttributes"],
order: {
apiAttributes: {
ordering: "ASC",
},
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;
const propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`);
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}"`;
}
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)),
];
const queryBuilder = repo.createQueryBuilder(tbMain);
// join กับตารารอง
if (propertyOtherKey.length > 0) {
propertyOtherKey.forEach((tb) => {
const relationName = relationMap[tb];
if (relationName) {
queryBuilder.leftJoin(
`${tbMain}.${relationName === "next_holder" ? "current_holder" : relationName}`, // เช็คว่าถ้าเป็น next_holder ให้ใช้ current_holder แทน
tb,
);
}
});
}
// // เพิ่ม Main.id เพราะจะใช้ pk ในการแมบและนับจำนวน
// if (!propertyKey.includes(`${Main}.id`)) {
// propertyKey.push(`${Main}.id`);
// }
// add FK
let pk: string = "";
const primaryColumns = metadata.primaryColumns;
primaryColumns.forEach((col) => {
pk = col.propertyName;
if (!propertyKey.includes(`${tbMain}.${pk}`)) {
propertyKey.push(`${tbMain}.${pk}`);
}
});
const [items, total] = await queryBuilder
.select(propertyKey)
.where(condition)
.orderBy(propertyKey[0], "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, ...x } = item;
return x;
});
// console.log("queryBuilder ===> ", queryBuilder.getQuery());
// 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;
}
});
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;
const propertyKey = apiName.apiAttributes.map((attr) => `${attr.tbName}.${attr.propertyKey}`);
const queryBuilder = AppDataSource.getRepository(Profile)
.createQueryBuilder("Profile")
.select(propertyKey);
const [items, total] = await queryBuilder
.skip(offset)
.take(pageSize)
.orderBy("Profile.createdAt", "DESC")
.getManyAndCount();
return new HttpSuccess({ items, total });
// } catch (error) {
// throw new HttpError(
// HttpStatusCode.INTERNAL_SERVER_ERROR,
// (error instanceof Error ? error.message : String(error)) ||
// "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
// );
// }
return flattenedItem;
});
return new HttpSuccess({ data: results, total });
}
}

View file

@ -6673,23 +6673,11 @@ export class CommandController extends Controller {
};
try {
// 8. บันทึกข้อมูลใหม่
const dataAct = new ProfileActposition();
Object.assign(dataAct, metaAct);
const historyAct = new ProfileActpositionHistory();
Object.assign(historyAct, { ...dataAct, id: undefined });
await this.actpositionRepository.save(dataAct);
historyAct.profileActpositionId = dataAct.id;
await this.actpositionHistoryRepository.save(historyAct);
// 9. ปิดสถานะรักษาการ
// 8. ปิดสถานะรักษาการ
const existingActPositions = await this.actpositionRepository.find({
where: {
profileId: item.posMasterChild.current_holderId,
status: true,
id: Not(dataAct.id),
},
});
@ -6702,6 +6690,17 @@ export class CommandController extends Controller {
await this.actpositionRepository.save(updatedActPositions);
}
// 9. บันทึกข้อมูลใหม่
const dataAct = new ProfileActposition();
Object.assign(dataAct, metaAct);
const historyAct = new ProfileActpositionHistory();
Object.assign(historyAct, { ...dataAct, id: undefined });
await this.actpositionRepository.save(dataAct);
historyAct.profileActpositionId = dataAct.id;
await this.actpositionHistoryRepository.save(historyAct);
} catch (error) {
console.error(`Error processing item ${item.id}:`, error);
throw new HttpError(

View file

@ -693,7 +693,11 @@ export class OrganizationDotnetController extends Controller {
}
}
}
let positionLeaveName = profile.posLevel?.posLevelName ?? null;
let positionLeaveName =
profile.posType == null && profile.posLevel == null
? ""
: `${profile.posType?.posTypeShortName ?? ""} ${profile.posLevel?.posLevelName ?? ""}`
const _profileCurrent = profile?.current_holders?.find(
(x) =>
x.orgRevision?.orgRevisionIsDraft === false &&

View file

@ -2650,70 +2650,72 @@ export class PositionController extends Controller {
async getHistoryPosMater(@Path() id: string, @Request() request: RequestWithUser) {
let _workflow = await new permission().Workflow(request, id, "SYS_ORG");
if (_workflow == false) await new permission().PermissionGet(request, "SYS_ORG");
const posMaster = await this.posMasterRepository.findOne({
where: { id },
});
// ใช้ query builder สำหรับประสิทธิภาพที่ดีขึ้น
const queryBuilder = this.posMasterRepository
.createQueryBuilder("pm")
.leftJoinAndSelect("pm.orgRevision", "orgRevision")
.leftJoinAndSelect("pm.orgRoot", "orgRoot")
.leftJoinAndSelect("pm.orgChild1", "orgChild1")
.leftJoinAndSelect("pm.orgChild2", "orgChild2")
.leftJoinAndSelect("pm.orgChild3", "orgChild3")
.leftJoinAndSelect("pm.orgChild4", "orgChild4")
.leftJoinAndSelect("pm.current_holder", "current_holder")
.leftJoinAndSelect("pm.positions", "positions")
.leftJoinAndSelect("positions.posLevel", "posLevel")
.leftJoinAndSelect("positions.posType", "posType")
.leftJoinAndSelect("positions.posExecutive", "posExecutive");
// หาข้อมูลหลัก
const posMaster = await queryBuilder.where("pm.id = :id", { id }).getOne();
if (!posMaster) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
}
const posMasters = await this.posMasterRepository.find({
where: {
ancestorDNA:
posMaster.ancestorDNA == null || posMaster.ancestorDNA == ""
? "123"
: posMaster.ancestorDNA,
// current_holderId: Not(IsNull()),
},
order: { lastUpdatedAt: "DESC" },
relations: [
"orgRoot",
"orgChild1",
"orgChild2",
"orgChild3",
"orgChild4",
"current_holder",
"positions",
"positions.posLevel",
"positions.posType",
"positions.posExecutive",
],
});
// สร้าง conditions
const ancestorDNA = posMaster.ancestorDNA || "123";
let historyQuery = queryBuilder.where("pm.ancestorDNA = :ancestorDNA", { ancestorDNA });
// เพิ่มเงื่อนไข draft
if (
posMaster.orgRevision?.orgRevisionIsCurrent != false &&
posMaster.orgRevision?.orgRevisionIsDraft != true
) {
historyQuery = historyQuery.andWhere("orgRevision.orgRevisionIsDraft != :isDraft", {
isDraft: true,
});
}
const posMasters = await historyQuery.orderBy("pm.lastUpdatedAt", "DESC").getMany();
const _data = posMasters.map((item) => ({
id: item.id,
orgShortName:
item.orgRoot == null
? null
: item.orgChild1 == null
? item.orgRoot.orgRootShortName
: item.orgChild2 == null
? item.orgChild1.orgChild1ShortName
: item.orgChild3 == null
? item.orgChild2.orgChild2ShortName
: item.orgChild4 == null
? item.orgChild3.orgChild3ShortName
: item.orgChild4.orgChild4ShortName,
lastUpdatedAt: item.lastUpdatedAt ? item.lastUpdatedAt : null,
posMasterNoPrefix: item.posMasterNoPrefix ? item.posMasterNoPrefix : null,
posMasterNo: item.posMasterNo ? item.posMasterNo : null,
posMasterNoSuffix: item.posMasterNoSuffix ? item.posMasterNoSuffix : null,
reason: item.reason ? item.reason : null,
item.orgChild4?.orgChild4ShortName ||
item.orgChild3?.orgChild3ShortName ||
item.orgChild2?.orgChild2ShortName ||
item.orgChild1?.orgChild1ShortName ||
item.orgRoot?.orgRootShortName ||
null,
lastUpdatedAt: item.lastUpdatedAt,
posMasterNoPrefix: item.posMasterNoPrefix,
posMasterNo: item.posMasterNo,
posMasterNoSuffix: item.posMasterNoSuffix,
reason: item.reason,
position: item.positions.map((x) => x.positionName).join("/"),
posExecutive: item.positions
.filter((x) => x.posExecutive != null)
.map((x) => x.posExecutive?.posExecutiveName ?? null)
.filter((x) => x.posExecutive)
.map((x) => x.posExecutive!.posExecutiveName)
.join("/"),
posLevel: item.positions.map((x) => x.posLevel.posLevelName).join("/"),
posType: item.positions.map((x) => x.posType.posTypeName).join("/"),
fullname:
(item?.current_holder?.prefix ?? "") +
"" +
(item?.current_holder?.firstName ?? "") +
" " +
(item?.current_holder?.lastName ?? ""),
`${item.current_holder?.prefix || ""}${item.current_holder?.firstName || ""} ${item.current_holder?.lastName || ""}`.trim(),
}));
return new HttpSuccess(_data);
}
/**
* API
*

View file

@ -0,0 +1,16 @@
type SystemCode = "registry" | "registry_emp" | "registry_temp" | "organization" | "position";
interface SystemDefinition {
code: SystemCode;
name: string;
}
interface EntityDefinition {
name: string;
repository: any;
description: string;
isMain?: boolean;
system: SystemCode[];
}
export { SystemCode, SystemDefinition, EntityDefinition };