ERP-node/backend-node/src/services/menuService.ts

246 lines
7.3 KiB
TypeScript
Raw Normal View History

import { getPool } from "../database/db";
import { logger } from "../utils/logger";
/**
*
*
*
*/
/**
* OBJID
* ( + )
*
* :
* - /
* - (3 )
* - (parent_obj_id = 0)
* -
*
* @param menuObjid OBJID
* @returns + OBJID ( , )
*
* @example
* // 영업관리 (200)
* // ├── 고객관리 (201)
* // │ └── 고객등록 (211)
* // ├── 계약관리 (202)
* // └── 주문관리 (203)
*
* await getSiblingMenuObjids(201);
* // 결과: [201, 202, 203, 211] - 형제(202, 203) + 자식(211)
*/
export async function getSiblingMenuObjids(menuObjid: number): Promise<number[]> {
const pool = getPool();
try {
logger.debug("메뉴 스코프 조회 시작", { menuObjid });
2025-11-12 17:52:08 +09:00
// 1. 현재 메뉴 정보 조회 (부모 ID 확인)
const currentMenuQuery = `
SELECT parent_obj_id FROM menu_info
WHERE objid = $1
`;
const currentMenuResult = await pool.query(currentMenuQuery, [menuObjid]);
if (currentMenuResult.rows.length === 0) {
logger.warn("메뉴를 찾을 수 없음, 자기 자신만 반환", { menuObjid });
return [menuObjid];
}
const parentObjId = Number(currentMenuResult.rows[0].parent_obj_id);
2025-11-12 17:52:08 +09:00
// 2. 최상위 메뉴(parent_obj_id = 0)는 자기 자신만 반환
if (parentObjId === 0) {
logger.debug("최상위 메뉴, 자기 자신만 반환", { menuObjid });
return [menuObjid];
}
// 3. 형제 메뉴들 조회 (같은 부모를 가진 메뉴들)
const siblingsQuery = `
SELECT objid FROM menu_info
WHERE parent_obj_id = $1
ORDER BY objid
`;
2025-11-12 17:52:08 +09:00
const siblingsResult = await pool.query(siblingsQuery, [parentObjId]);
const siblingObjids = siblingsResult.rows.map((row) => Number(row.objid));
2025-11-12 17:52:08 +09:00
// 4. 각 형제 메뉴(자기 자신 포함)의 자식 메뉴들도 조회
const allObjids = [...siblingObjids];
for (const siblingObjid of siblingObjids) {
const childrenQuery = `
SELECT objid FROM menu_info
WHERE parent_obj_id = $1
ORDER BY objid
`;
const childrenResult = await pool.query(childrenQuery, [siblingObjid]);
const childObjids = childrenResult.rows.map((row) => Number(row.objid));
allObjids.push(...childObjids);
}
2025-11-12 17:52:08 +09:00
// 5. 중복 제거 및 정렬
const uniqueObjids = Array.from(new Set(allObjids)).sort((a, b) => a - b);
logger.debug("메뉴 스코프 조회 완료", {
2025-11-12 17:52:08 +09:00
menuObjid,
parentObjId,
siblingCount: siblingObjids.length,
totalCount: uniqueObjids.length
});
2025-11-12 17:52:08 +09:00
return uniqueObjids;
} catch (error: any) {
logger.error("메뉴 스코프 조회 실패", {
menuObjid,
error: error.message,
stack: error.stack
2025-11-28 14:56:11 +09:00
});
// 에러 발생 시 안전하게 자기 자신만 반환
return [menuObjid];
}
}
/**
* OBJID
*
* , .
* .
*
* @param menuObjid OBJID
* @returns + OBJID ()
*
* @example
* // 메뉴 구조:
* // └── 구매관리 (100)
* // ├── 공급업체관리 (101)
* // ├── 발주관리 (102)
* // └── 입고관리 (103)
* // └── 입고상세 (104)
*
* await getMenuAndChildObjids(100);
* // 결과: [100, 101, 102, 103, 104]
*/
export async function getMenuAndChildObjids(menuObjid: number): Promise<number[]> {
const pool = getPool();
try {
logger.debug("메뉴 및 하위 메뉴 조회 시작", { menuObjid });
// 재귀 CTE를 사용하여 선택한 메뉴와 모든 하위 메뉴 조회
const query = `
WITH RECURSIVE menu_tree AS (
-- 시작점: 선택한
SELECT objid, parent_obj_id, 1 AS depth
FROM menu_info
WHERE objid = $1
UNION ALL
-- 재귀: 하위
SELECT m.objid, m.parent_obj_id, mt.depth + 1
FROM menu_info m
INNER JOIN menu_tree mt ON m.parent_obj_id = mt.objid
WHERE mt.depth < 10 --
)
SELECT objid FROM menu_tree ORDER BY depth, objid
`;
const result = await pool.query(query, [menuObjid]);
const objids = result.rows.map((row) => Number(row.objid));
logger.debug("메뉴 및 하위 메뉴 조회 완료", {
menuObjid,
totalCount: objids.length,
objids
});
return objids;
} catch (error: any) {
logger.error("메뉴 및 하위 메뉴 조회 실패", {
menuObjid,
error: error.message,
stack: error.stack
});
// 에러 발생 시 안전하게 자기 자신만 반환
return [menuObjid];
}
}
/**
* OBJID
*
*
*
* @param menuObjids OBJID
* @returns OBJID ( , )
*
* @example
* // 서로 다른 부모를 가진 메뉴들의 형제를 모두 조회
* await getAllSiblingMenuObjids([201, 301]);
* // 201의 형제: [201, 202, 203]
* // 301의 형제: [301, 302]
* // 결과: [201, 202, 203, 301, 302]
*/
export async function getAllSiblingMenuObjids(
menuObjids: number[]
): Promise<number[]> {
if (!menuObjids || menuObjids.length === 0) {
logger.warn("getAllSiblingMenuObjids: 빈 배열 입력");
return [];
}
const allSiblings = new Set<number>();
for (const objid of menuObjids) {
const siblings = await getSiblingMenuObjids(objid);
siblings.forEach((s) => allSiblings.add(s));
}
const result = Array.from(allSiblings).sort((a, b) => a - b);
logger.info("여러 메뉴의 형제 조회 완료", {
inputMenus: menuObjids,
resultCount: result.length,
result,
});
return result;
}
/**
*
*
* @param menuObjid OBJID
* @returns ( null)
*/
export async function getMenuInfo(menuObjid: number): Promise<any | null> {
const pool = getPool();
try {
const query = `
SELECT
objid,
parent_obj_id AS "parentObjId",
menu_name_kor AS "menuNameKor",
menu_name_eng AS "menuNameEng",
menu_url AS "menuUrl",
company_code AS "companyCode"
FROM menu_info
WHERE objid = $1
`;
const result = await pool.query(query, [menuObjid]);
if (result.rows.length === 0) {
return null;
}
return result.rows[0];
} catch (error: any) {
logger.error("메뉴 정보 조회 실패", { menuObjid, error: error.message });
return null;
}
}