공통코드 계층구조 구현
This commit is contained in:
@@ -25,6 +25,8 @@ export interface CodeInfo {
|
||||
is_active: string;
|
||||
company_code: string;
|
||||
menu_objid?: number | null; // 메뉴 기반 코드 관리용
|
||||
parent_code_value?: string | null; // 계층구조: 부모 코드값
|
||||
depth?: number; // 계층구조: 깊이 (1, 2, 3단계)
|
||||
created_date?: Date | null;
|
||||
created_by?: string | null;
|
||||
updated_date?: Date | null;
|
||||
@@ -61,6 +63,8 @@ export interface CreateCodeData {
|
||||
description?: string;
|
||||
sortOrder?: number;
|
||||
isActive?: string;
|
||||
parentCodeValue?: string; // 계층구조: 부모 코드값
|
||||
depth?: number; // 계층구조: 깊이 (1, 2, 3단계)
|
||||
}
|
||||
|
||||
export class CommonCodeService {
|
||||
@@ -405,11 +409,22 @@ export class CommonCodeService {
|
||||
menuObjid: number
|
||||
) {
|
||||
try {
|
||||
// 계층구조: depth 계산 (부모가 있으면 부모의 depth + 1, 없으면 1)
|
||||
let depth = 1;
|
||||
if (data.parentCodeValue) {
|
||||
const parentCode = await queryOne<CodeInfo>(
|
||||
`SELECT depth FROM code_info
|
||||
WHERE code_category = $1 AND code_value = $2 AND company_code = $3`,
|
||||
[categoryCode, data.parentCodeValue, companyCode]
|
||||
);
|
||||
depth = parentCode ? (parentCode.depth || 1) + 1 : 1;
|
||||
}
|
||||
|
||||
const code = await queryOne<CodeInfo>(
|
||||
`INSERT INTO code_info
|
||||
(code_category, code_value, code_name, code_name_eng, description, sort_order,
|
||||
is_active, menu_objid, company_code, created_by, updated_by, created_date, updated_date)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, NOW(), NOW())
|
||||
is_active, menu_objid, company_code, parent_code_value, depth, created_by, updated_by, created_date, updated_date)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, $11, $12, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[
|
||||
categoryCode,
|
||||
@@ -420,13 +435,15 @@ export class CommonCodeService {
|
||||
data.sortOrder || 0,
|
||||
menuObjid,
|
||||
companyCode,
|
||||
data.parentCodeValue || null,
|
||||
depth,
|
||||
createdBy,
|
||||
createdBy,
|
||||
]
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode})`
|
||||
`코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode}, 부모: ${data.parentCodeValue || '없음'}, 깊이: ${depth})`
|
||||
);
|
||||
return code;
|
||||
} catch (error) {
|
||||
@@ -491,6 +508,24 @@ export class CommonCodeService {
|
||||
updateFields.push(`is_active = $${paramIndex++}`);
|
||||
values.push(activeValue);
|
||||
}
|
||||
// 계층구조: 부모 코드값 수정
|
||||
if (data.parentCodeValue !== undefined) {
|
||||
updateFields.push(`parent_code_value = $${paramIndex++}`);
|
||||
values.push(data.parentCodeValue || null);
|
||||
|
||||
// depth도 함께 업데이트
|
||||
let newDepth = 1;
|
||||
if (data.parentCodeValue) {
|
||||
const parentCode = await queryOne<CodeInfo>(
|
||||
`SELECT depth FROM code_info
|
||||
WHERE code_category = $1 AND code_value = $2`,
|
||||
[categoryCode, data.parentCodeValue]
|
||||
);
|
||||
newDepth = parentCode ? (parentCode.depth || 1) + 1 : 1;
|
||||
}
|
||||
updateFields.push(`depth = $${paramIndex++}`);
|
||||
values.push(newDepth);
|
||||
}
|
||||
|
||||
// WHERE 절 구성
|
||||
let whereClause = `WHERE code_category = $${paramIndex++} AND code_value = $${paramIndex}`;
|
||||
@@ -847,4 +882,170 @@ export class CommonCodeService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 계층구조 코드 조회 (특정 depth 또는 부모코드 기준)
|
||||
* @param categoryCode 카테고리 코드
|
||||
* @param parentCodeValue 부모 코드값 (없으면 최상위 코드만 조회)
|
||||
* @param depth 특정 깊이만 조회 (선택)
|
||||
*/
|
||||
async getHierarchicalCodes(
|
||||
categoryCode: string,
|
||||
parentCodeValue?: string | null,
|
||||
depth?: number,
|
||||
userCompanyCode?: string,
|
||||
menuObjid?: number
|
||||
) {
|
||||
try {
|
||||
const whereConditions: string[] = ["code_category = $1", "is_active = 'Y'"];
|
||||
const values: any[] = [categoryCode];
|
||||
let paramIndex = 2;
|
||||
|
||||
// 부모 코드값 필터링
|
||||
if (parentCodeValue === null || parentCodeValue === undefined) {
|
||||
// 최상위 코드 (부모가 없는 코드)
|
||||
whereConditions.push("(parent_code_value IS NULL OR parent_code_value = '')");
|
||||
} else if (parentCodeValue !== '') {
|
||||
whereConditions.push(`parent_code_value = $${paramIndex}`);
|
||||
values.push(parentCodeValue);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 특정 깊이 필터링
|
||||
if (depth !== undefined) {
|
||||
whereConditions.push(`depth = $${paramIndex}`);
|
||||
values.push(depth);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 메뉴별 필터링 (형제 메뉴 포함)
|
||||
if (menuObjid) {
|
||||
const { getSiblingMenuObjids } = await import('./menuService');
|
||||
const siblingMenuObjids = await getSiblingMenuObjids(menuObjid);
|
||||
whereConditions.push(`menu_objid = ANY($${paramIndex})`);
|
||||
values.push(siblingMenuObjids);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 회사별 필터링
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
whereConditions.push(`company_code = $${paramIndex}`);
|
||||
values.push(userCompanyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
||||
|
||||
const codes = await query<CodeInfo>(
|
||||
`SELECT * FROM code_info
|
||||
${whereClause}
|
||||
ORDER BY sort_order ASC, code_value ASC`,
|
||||
values
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`계층구조 코드 조회: ${categoryCode}, 부모: ${parentCodeValue || '최상위'}, 깊이: ${depth || '전체'} - ${codes.length}개`
|
||||
);
|
||||
|
||||
return codes;
|
||||
} catch (error) {
|
||||
logger.error(`계층구조 코드 조회 중 오류 (${categoryCode}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 계층구조 코드 트리 전체 조회 (카테고리 기준)
|
||||
*/
|
||||
async getCodeTree(
|
||||
categoryCode: string,
|
||||
userCompanyCode?: string,
|
||||
menuObjid?: number
|
||||
) {
|
||||
try {
|
||||
const whereConditions: string[] = ["code_category = $1", "is_active = 'Y'"];
|
||||
const values: any[] = [categoryCode];
|
||||
let paramIndex = 2;
|
||||
|
||||
// 메뉴별 필터링 (형제 메뉴 포함)
|
||||
if (menuObjid) {
|
||||
const { getSiblingMenuObjids } = await import('./menuService');
|
||||
const siblingMenuObjids = await getSiblingMenuObjids(menuObjid);
|
||||
whereConditions.push(`menu_objid = ANY($${paramIndex})`);
|
||||
values.push(siblingMenuObjids);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 회사별 필터링
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
whereConditions.push(`company_code = $${paramIndex}`);
|
||||
values.push(userCompanyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const whereClause = `WHERE ${whereConditions.join(" AND ")}`;
|
||||
|
||||
const allCodes = await query<CodeInfo>(
|
||||
`SELECT * FROM code_info
|
||||
${whereClause}
|
||||
ORDER BY depth ASC, sort_order ASC, code_value ASC`,
|
||||
values
|
||||
);
|
||||
|
||||
// 트리 구조로 변환
|
||||
const buildTree = (codes: CodeInfo[], parentValue: string | null = null): any[] => {
|
||||
return codes
|
||||
.filter(code => {
|
||||
const codeParent = code.parent_code_value || null;
|
||||
return codeParent === parentValue;
|
||||
})
|
||||
.map(code => ({
|
||||
...code,
|
||||
children: buildTree(codes, code.code_value)
|
||||
}));
|
||||
};
|
||||
|
||||
const tree = buildTree(allCodes);
|
||||
|
||||
logger.info(
|
||||
`코드 트리 조회 완료: ${categoryCode} - 전체 ${allCodes.length}개`
|
||||
);
|
||||
|
||||
return {
|
||||
flat: allCodes,
|
||||
tree
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`코드 트리 조회 중 오류 (${categoryCode}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 자식 코드가 있는지 확인
|
||||
*/
|
||||
async hasChildren(
|
||||
categoryCode: string,
|
||||
codeValue: string,
|
||||
companyCode?: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
let sql = `SELECT COUNT(*) as count FROM code_info
|
||||
WHERE code_category = $1 AND parent_code_value = $2`;
|
||||
const values: any[] = [categoryCode, codeValue];
|
||||
|
||||
if (companyCode && companyCode !== "*") {
|
||||
sql += ` AND company_code = $3`;
|
||||
values.push(companyCode);
|
||||
}
|
||||
|
||||
const result = await queryOne<{ count: string }>(sql, values);
|
||||
const count = parseInt(result?.count || "0");
|
||||
|
||||
return count > 0;
|
||||
} catch (error) {
|
||||
logger.error(`자식 코드 확인 중 오류 (${categoryCode}.${codeValue}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user