add org chart by root id

This commit is contained in:
Warunee Tamkoo 2025-07-24 16:18:41 +07:00
parent 2ec55789a7
commit 554e3f2e22

View file

@ -3353,7 +3353,13 @@ export class OrganizationController extends Controller {
"orgChild3",
"orgChild4",
],
order: { posMasterOrder: "ASC" },
order: {
orgRoot: {
orgRootOrder: "ASC",
},
posMasterOrder: "ASC",
posMasterNo: "ASC",
},
});
// แยกข้อมูลด้วย JavaScript แทนการใช้ database queries หลาย ๆ ครั้ง
@ -3883,6 +3889,327 @@ export class OrganizationController extends Controller {
}
}
/**
* API Organizational Chart
*
* @summary Organizational Chart
*
* @param {string} revisionId Id revison
*/
@Get("org-chart/{revisionId}/{rootId}")
async orgChartByRoot(@Path() revisionId: string, @Path() rootId: string) {
// get revision data
const data = await this.orgRevisionRepository.findOne({ where: { id: revisionId } });
if (!data) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลโครงสร้าง");
}
type OrgInfo = { shortName: string; name: string; id: string };
let fieldId: "current_holderId" | "next_holderId" = "current_holderId";
let relations: string[] =
rootId === "root"
? ["current_holder", "orgRoot"]
: ["current_holder", "orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"];
if (data.orgRevisionIsCurrent === false && data.orgRevisionIsDraft === true) {
fieldId = "next_holderId";
relations =
rootId === "root"
? ["next_holder", "orgRoot"]
: ["next_holder", "orgRoot", "orgChild1", "orgChild2", "orgChild3", "orgChild4"];
}
const where =
rootId === "root"
? { orgRevisionId: data.id, posMasterNo: 1 }
: { orgRevisionId: data.id, orgRootId: rootId };
const allPosMasters = await this.posMasterRepository.find({
where,
relations,
order: {
orgRoot: { orgRootOrder: "ASC" },
posMasterOrder: "ASC",
posMasterNo: "ASC",
},
});
// Split positions by level
const posMasterRoot = allPosMasters.filter((item) => item.orgChild1Id === null);
const posMasterChild1 =
rootId !== "root"
? allPosMasters.filter((item) => item.orgChild2Id === null && item.orgChild1Id !== null)
: [];
const posMasterChild2 =
rootId !== "root"
? allPosMasters.filter((item) => item.orgChild3Id === null && item.orgChild2Id !== null)
: [];
const posMasterChild3 =
rootId !== "root"
? allPosMasters.filter((item) => item.orgChild4Id === null && item.orgChild3Id !== null)
: [];
const posMasterChild4 =
rootId !== "root" ? allPosMasters.filter((item) => item.orgChild4Id !== null) : [];
// Find the minimum posMasterNo for each orgRootId
const minPosMasterNoByOrgRootId = new Map<string, number>();
posMasterRoot.forEach((x) => {
const orgRootId = x.orgRootId ?? "";
const currentMin = minPosMasterNoByOrgRootId.get(orgRootId);
if (
(currentMin === undefined || x.posMasterNo < currentMin) &&
(x as any)[fieldId] != null &&
x.isDirector
) {
minPosMasterNoByOrgRootId.set(orgRootId, x.posMasterNo);
}
});
// Utility to group by key
function groupBy<T>(
arr: T[],
keyFn: (item: T) => string | number | undefined | null,
): Map<string, T[]> {
const map = new Map<string, T[]>();
for (const item of arr) {
let key = keyFn(item);
if (key === undefined || key === null) key = "";
key = String(key);
if (!map.has(key)) map.set(key, []);
map.get(key)!.push(item);
}
return map;
}
// Build lookup maps
const rootByOrgRootId = groupBy(
posMasterRoot.filter(
(item) =>
((item as any)[fieldId] && !item.isDirector) ||
(item.isDirector &&
minPosMasterNoByOrgRootId.get(item.orgRootId ?? "") !== item.posMasterNo),
),
(item) => item.orgRootId ?? "",
);
const child1ByOrgRootId = groupBy(
posMasterChild1.filter((item) => item.isDirector),
(item) => item.orgRootId ?? "",
);
const child1ByOrgChild1Id = groupBy(
posMasterChild1.filter((item) => (item as any)[fieldId] && !item.isDirector),
(item) => item.orgChild1Id ?? "",
);
const child2ByOrgChild1Id = groupBy(
posMasterChild2.filter((item) => item.isDirector),
(item) => item.orgChild1Id ?? "",
);
const child2ByOrgChild2Id = groupBy(
posMasterChild2.filter((item) => (item as any)[fieldId] && !item.isDirector),
(item) => item.orgChild2Id ?? "",
);
const child3ByOrgChild2Id = groupBy(
posMasterChild3.filter((item) => item.isDirector),
(item) => item.orgChild2Id ?? "",
);
const child3ByOrgChild3Id = groupBy(
posMasterChild3.filter((item) => (item as any)[fieldId] && !item.isDirector),
(item) => item.orgChild3Id ?? "",
);
const child4ByOrgChild3Id = groupBy(
posMasterChild4.filter((item) => item.isDirector),
(item) => item.orgChild3Id ?? "",
);
// Helper to create node
function createNode(item: any, level: number, orgInfo: OrgInfo): any {
const holder = fieldId === "current_holderId" ? item.current_holder : item.next_holder;
return {
level,
personID: holder?.id ?? "",
name: holder ? `${holder.firstName} ${holder.lastName}` : "ว่าง",
avatar:
holder?.avatar && holder?.avatarName ? `${holder.avatar}/${holder.avatarName}` : null,
positionName: holder?.position ?? "",
positionNum: `${orgInfo.shortName} ${item.posMasterNo}`,
positionNumInt: item.posMasterNo,
departmentName: orgInfo.name,
organizationId: orgInfo.id,
children: [],
};
}
// Recursive builder for children
function buildChildren(level: number, parent: any, orgInfo: OrgInfo): any[] {
if (level === 2) {
// Level 2: child1 non-directors and child2 directors
const children: any[] = [];
// Root non-directors
(
rootByOrgRootId
.get(parent.orgRootId)
?.filter(
(x00) =>
!x00.isDirector ||
(x00.isDirector &&
minPosMasterNoByOrgRootId.get(x00.orgRootId ?? "") !== x00.posMasterNo),
) || []
).forEach((x00) => {
children.push(
createNode(x00, 2, {
shortName: x00.orgRoot.orgRootShortName,
name: x00.orgRoot.orgRootName,
id: x00.orgRoot.id,
}),
);
});
if (rootId !== "root") {
// Child1 directors
(child1ByOrgRootId.get(parent.orgRootId) || []).forEach((x1) => {
const childLevel2 = buildChildren(3, x1, {
shortName: x1.orgChild1.orgChild1ShortName,
name: x1.orgChild1.orgChild1Name,
id: x1.orgChild1.id,
});
const node = createNode(x1, 2, {
shortName: x1.orgChild1.orgChild1ShortName,
name: x1.orgChild1.orgChild1Name,
id: x1.orgChild1.id,
});
node.children = childLevel2;
children.push(node);
});
}
return children;
} else if (level === 3) {
// Level 3: child1 non-directors and child2 directors
const children: any[] = [];
(child1ByOrgChild1Id.get(parent.orgChild1Id) || []).forEach((x11) => {
children.push(
createNode(x11, 3, {
shortName: x11.orgChild1.orgChild1ShortName,
name: x11.orgChild1.orgChild1Name,
id: x11.orgChild1.id,
}),
);
});
(child2ByOrgChild1Id.get(parent.orgChild1Id) || []).forEach((x2) => {
const childLevel3 = buildChildren(4, x2, {
shortName: x2.orgChild2.orgChild2ShortName,
name: x2.orgChild2.orgChild2Name,
id: x2.orgChild2.id,
});
const node = createNode(x2, 3, {
shortName: x2.orgChild2.orgChild2ShortName,
name: x2.orgChild2.orgChild2Name,
id: x2.orgChild2.id,
});
node.children = childLevel3;
children.push(node);
});
return children;
} else if (level === 4) {
// Level 4: child2 non-directors and child3 directors
const children: any[] = [];
(child2ByOrgChild2Id.get(parent.orgChild2Id) || []).forEach((x22) => {
children.push(
createNode(x22, 4, {
shortName: x22.orgChild2.orgChild2ShortName,
name: x22.orgChild2.orgChild2Name,
id: x22.orgChild2.id,
}),
);
});
(child3ByOrgChild2Id.get(parent.orgChild2Id) || []).forEach((x3) => {
const childLevel4 = buildChildren(5, x3, {
shortName: x3.orgChild3.orgChild3ShortName,
name: x3.orgChild3.orgChild3Name,
id: x3.orgChild3.id,
});
const node = createNode(x3, 4, {
shortName: x3.orgChild3.orgChild3ShortName,
name: x3.orgChild3.orgChild3Name,
id: x3.orgChild3.id,
});
node.children = childLevel4;
children.push(node);
});
return children;
} else if (level === 5) {
// Level 5: child3 non-directors and child4 directors
const children: any[] = [];
(child3ByOrgChild3Id.get(parent.orgChild3Id) || []).forEach((x33) => {
children.push(
createNode(x33, 5, {
shortName: x33.orgChild3.orgChild3ShortName,
name: x33.orgChild3.orgChild3Name,
id: x33.orgChild3.id,
}),
);
});
(child4ByOrgChild3Id.get(parent.orgChild3Id) || []).forEach((x4) => {
// Level 5: child4 directors and their non-directors
const childLevel5: any[] = [];
posMasterChild4
.filter((x) => !x.isDirector && (x as any)[fieldId] && x.orgChild3Id === x4.orgChild3Id)
.forEach((x44) => {
childLevel5.push(
createNode(x44, 5, {
shortName: x44.orgChild4.orgChild4ShortName,
name: x44.orgChild4.orgChild4Name,
id: x44.orgChild4.id,
}),
);
});
const node = createNode(x4, 4, {
shortName: x4.orgChild4.orgChild4ShortName,
name: x4.orgChild4.orgChild4Name,
id: x4.orgChild4.id,
});
node.children = childLevel5;
children.push(node);
});
return children;
}
return [];
}
// Build root nodes
const formattedData = posMasterRoot
.filter(
(x: any) =>
x.isDirector && minPosMasterNoByOrgRootId.get(x.orgRootId ?? "") === x.posMasterNo,
)
.map((x0) => {
const rootNode = createNode(x0, 1, {
shortName: x0.orgRoot.orgRootShortName,
name: x0.orgRoot.orgRootName,
id: x0.orgRoot.id,
});
rootNode.children = buildChildren(2, x0, {
shortName: x0.orgRoot.orgRootShortName,
name: x0.orgRoot.orgRootName,
id: x0.orgRoot.id,
});
return rootNode;
});
const formattedData_ =
rootId === "root"
? {
personID: "",
name: "",
avatar: "",
positionName: "",
positionNum: "",
positionNumInt: null,
departmentName: data.orgRevisionName,
organizationId: data.id,
children: formattedData,
}
: formattedData;
return new HttpSuccess([formattedData_]);
}
/**
* API Organizational StructChart
*