feat: 채번규칙 메뉴 스코프 전환 완료
✅ 주요 변경사항: - 백엔드: menuService.ts 추가 (형제 메뉴 조회 유틸리티) - 백엔드: numberingRuleService.getAvailableRulesForMenu() 메뉴 스코프 적용 - 백엔드: tableCategoryValueService 메뉴 스코프 준비 (menuObjid 파라미터 추가) - 프론트엔드: TextInputConfigPanel에 부모 메뉴 선택 UI 추가 - 프론트엔드: 메뉴별 채번규칙 필터링 (형제 메뉴 공유) 🔧 기술 세부사항: - getSiblingMenuObjids(): 같은 부모를 가진 형제 메뉴 OBJID 조회 - 채번규칙 우선순위: menu (형제) > table > global - 사용자 메뉴(menu_type='1') 레벨 2만 부모 메뉴로 선택 가능 📝 다음 단계: - 카테고리 컴포넌트도 메뉴 스코프로 전환 예정
This commit is contained in:
159
backend-node/src/services/menuService.ts
Normal file
159
backend-node/src/services/menuService.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* 메뉴 관련 유틸리티 서비스
|
||||
*
|
||||
* 메뉴 스코프 기반 데이터 공유를 위한 형제 메뉴 조회 기능 제공
|
||||
*/
|
||||
|
||||
/**
|
||||
* 메뉴의 형제 메뉴 OBJID 목록 조회
|
||||
* (같은 부모를 가진 메뉴들)
|
||||
*
|
||||
* 메뉴 스코프 규칙:
|
||||
* - 같은 부모를 가진 형제 메뉴들은 카테고리/채번규칙을 공유
|
||||
* - 최상위 메뉴(parent_obj_id = 0)는 자기 자신만 반환
|
||||
* - 메뉴를 찾을 수 없으면 안전하게 자기 자신만 반환
|
||||
*
|
||||
* @param menuObjid 현재 메뉴의 OBJID
|
||||
* @returns 형제 메뉴 OBJID 배열 (자기 자신 포함, 정렬됨)
|
||||
*
|
||||
* @example
|
||||
* // 영업관리 (200)
|
||||
* // ├── 고객관리 (201)
|
||||
* // ├── 계약관리 (202)
|
||||
* // └── 주문관리 (203)
|
||||
*
|
||||
* await getSiblingMenuObjids(201);
|
||||
* // 결과: [201, 202, 203] - 모두 같은 부모(200)를 가진 형제
|
||||
*/
|
||||
export async function getSiblingMenuObjids(menuObjid: number): Promise<number[]> {
|
||||
const pool = getPool();
|
||||
|
||||
try {
|
||||
logger.info("형제 메뉴 조회 시작", { menuObjid });
|
||||
|
||||
// 1. 현재 메뉴의 부모 찾기
|
||||
const parentQuery = `
|
||||
SELECT parent_obj_id FROM menu_info WHERE objid = $1
|
||||
`;
|
||||
const parentResult = await pool.query(parentQuery, [menuObjid]);
|
||||
|
||||
if (parentResult.rows.length === 0) {
|
||||
logger.warn("메뉴를 찾을 수 없음, 자기 자신만 반환", { menuObjid });
|
||||
return [menuObjid]; // 메뉴가 없으면 안전하게 자기 자신만 반환
|
||||
}
|
||||
|
||||
const parentObjId = parentResult.rows[0].parent_obj_id;
|
||||
|
||||
if (!parentObjId || parentObjId === 0) {
|
||||
// 최상위 메뉴인 경우 자기 자신만 반환
|
||||
logger.info("최상위 메뉴 (형제 없음)", { menuObjid, parentObjId });
|
||||
return [menuObjid];
|
||||
}
|
||||
|
||||
// 2. 같은 부모를 가진 형제 메뉴들 조회
|
||||
const siblingsQuery = `
|
||||
SELECT objid FROM menu_info
|
||||
WHERE parent_obj_id = $1
|
||||
ORDER BY objid
|
||||
`;
|
||||
const siblingsResult = await pool.query(siblingsQuery, [parentObjId]);
|
||||
|
||||
const siblingObjids = siblingsResult.rows.map((row) => Number(row.objid));
|
||||
|
||||
logger.info("형제 메뉴 조회 완료", {
|
||||
menuObjid,
|
||||
parentObjId,
|
||||
siblingCount: siblingObjids.length,
|
||||
siblings: siblingObjids,
|
||||
});
|
||||
|
||||
return siblingObjids;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user