|
|
|
|
@@ -15,6 +15,7 @@ export interface MenuCopyResult {
|
|
|
|
|
copiedNumberingRules: number;
|
|
|
|
|
copiedCategoryMappings: number;
|
|
|
|
|
copiedTableTypeColumns: number; // 테이블 타입관리 입력타입 설정
|
|
|
|
|
copiedCascadingRelations: number; // 연쇄관계 설정
|
|
|
|
|
menuIdMap: Record<number, number>;
|
|
|
|
|
screenIdMap: Record<number, number>;
|
|
|
|
|
flowIdMap: Record<number, number>;
|
|
|
|
|
@@ -29,6 +30,7 @@ export interface AdditionalCopyOptions {
|
|
|
|
|
copyNumberingRules?: boolean;
|
|
|
|
|
copyCategoryMapping?: boolean;
|
|
|
|
|
copyTableTypeColumns?: boolean; // 테이블 타입관리 입력타입 설정
|
|
|
|
|
copyCascadingRelation?: boolean; // 연쇄관계 설정
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -754,28 +756,44 @@ export class MenuCopyService {
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// === 2.5단계: 채번 규칙 복사 (화면 복사 전에 실행하여 참조 업데이트 가능) ===
|
|
|
|
|
// 변수 초기화
|
|
|
|
|
let copiedCodeCategories = 0;
|
|
|
|
|
let copiedCodes = 0;
|
|
|
|
|
let copiedNumberingRules = 0;
|
|
|
|
|
let copiedCategoryMappings = 0;
|
|
|
|
|
let copiedTableTypeColumns = 0;
|
|
|
|
|
let copiedCascadingRelations = 0;
|
|
|
|
|
let numberingRuleIdMap = new Map<string, string>();
|
|
|
|
|
|
|
|
|
|
const menuObjids = menus.map((m) => m.objid);
|
|
|
|
|
|
|
|
|
|
// 메뉴 ID 맵을 먼저 생성 (채번 규칙 복사에 필요)
|
|
|
|
|
// 메뉴 ID 맵을 먼저 생성 (일관된 ID 사용을 위해)
|
|
|
|
|
const tempMenuIdMap = new Map<number, number>();
|
|
|
|
|
let tempObjId = await this.getNextMenuObjid(client);
|
|
|
|
|
for (const menu of menus) {
|
|
|
|
|
tempMenuIdMap.set(menu.objid, tempObjId++);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 3단계: 메뉴 복사 (외래키 의존성 해결을 위해 먼저 실행) ===
|
|
|
|
|
// 채번 규칙, 코드 카테고리 등이 menu_info를 참조하므로 메뉴를 먼저 생성
|
|
|
|
|
logger.info("\n📂 [3단계] 메뉴 복사 (외래키 선행 조건)");
|
|
|
|
|
const menuIdMap = await this.copyMenus(
|
|
|
|
|
menus,
|
|
|
|
|
sourceMenuObjid,
|
|
|
|
|
sourceCompanyCode,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
new Map(), // screenIdMap은 아직 없음 (나중에 할당에서 처리)
|
|
|
|
|
userId,
|
|
|
|
|
client,
|
|
|
|
|
tempMenuIdMap
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// === 4단계: 채번 규칙 복사 (메뉴 복사 후, 화면 복사 전) ===
|
|
|
|
|
if (additionalCopyOptions?.copyNumberingRules) {
|
|
|
|
|
logger.info("\n📦 [2.5단계] 채번 규칙 복사 (화면 복사 전)");
|
|
|
|
|
logger.info("\n📦 [4단계] 채번 규칙 복사");
|
|
|
|
|
const ruleResult = await this.copyNumberingRulesWithMap(
|
|
|
|
|
menuObjids,
|
|
|
|
|
tempMenuIdMap,
|
|
|
|
|
menuIdMap, // 실제 생성된 메뉴 ID 사용
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
@@ -784,8 +802,46 @@ export class MenuCopyService {
|
|
|
|
|
numberingRuleIdMap = ruleResult.ruleIdMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 3단계: 화면 복사 ===
|
|
|
|
|
logger.info("\n📄 [3단계] 화면 복사");
|
|
|
|
|
// === 4.1단계: 코드 카테고리 + 코드 복사 ===
|
|
|
|
|
if (additionalCopyOptions?.copyCodeCategory) {
|
|
|
|
|
logger.info("\n📦 [4.1단계] 코드 카테고리 + 코드 복사");
|
|
|
|
|
const codeResult = await this.copyCodeCategoriesAndCodes(
|
|
|
|
|
menuObjids,
|
|
|
|
|
menuIdMap,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
copiedCodeCategories = codeResult.copiedCategories;
|
|
|
|
|
copiedCodes = codeResult.copiedCodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 4.2단계: 카테고리 매핑 + 값 복사 ===
|
|
|
|
|
if (additionalCopyOptions?.copyCategoryMapping) {
|
|
|
|
|
logger.info("\n📦 [4.2단계] 카테고리 매핑 + 값 복사");
|
|
|
|
|
copiedCategoryMappings = await this.copyCategoryMappingsAndValues(
|
|
|
|
|
menuObjids,
|
|
|
|
|
menuIdMap,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 4.3단계: 연쇄관계 복사 ===
|
|
|
|
|
if (additionalCopyOptions?.copyCascadingRelation) {
|
|
|
|
|
logger.info("\n📦 [4.3단계] 연쇄관계 복사");
|
|
|
|
|
copiedCascadingRelations = await this.copyCascadingRelations(
|
|
|
|
|
sourceCompanyCode,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
menuIdMap,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 5단계: 화면 복사 ===
|
|
|
|
|
logger.info("\n📄 [5단계] 화면 복사");
|
|
|
|
|
const screenIdMap = await this.copyScreens(
|
|
|
|
|
screenIds,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
@@ -796,20 +852,8 @@ export class MenuCopyService {
|
|
|
|
|
numberingRuleIdMap
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// === 4단계: 메뉴 복사 ===
|
|
|
|
|
logger.info("\n📂 [4단계] 메뉴 복사");
|
|
|
|
|
const menuIdMap = await this.copyMenus(
|
|
|
|
|
menus,
|
|
|
|
|
sourceMenuObjid, // 원본 최상위 메뉴 ID 전달
|
|
|
|
|
sourceCompanyCode,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
screenIdMap,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// === 5단계: 화면-메뉴 할당 ===
|
|
|
|
|
logger.info("\n🔗 [5단계] 화면-메뉴 할당");
|
|
|
|
|
// === 6단계: 화면-메뉴 할당 ===
|
|
|
|
|
logger.info("\n🔗 [6단계] 화면-메뉴 할당");
|
|
|
|
|
await this.createScreenMenuAssignments(
|
|
|
|
|
menus,
|
|
|
|
|
menuIdMap,
|
|
|
|
|
@@ -818,44 +862,15 @@ export class MenuCopyService {
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// === 6단계: 추가 복사 옵션 처리 (코드 카테고리, 카테고리 매핑) ===
|
|
|
|
|
if (additionalCopyOptions) {
|
|
|
|
|
// 6-1. 코드 카테고리 + 코드 복사
|
|
|
|
|
if (additionalCopyOptions.copyCodeCategory) {
|
|
|
|
|
logger.info("\n📦 [6-1단계] 코드 카테고리 + 코드 복사");
|
|
|
|
|
const codeResult = await this.copyCodeCategoriesAndCodes(
|
|
|
|
|
menuObjids,
|
|
|
|
|
menuIdMap,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
copiedCodeCategories = codeResult.copiedCategories;
|
|
|
|
|
copiedCodes = codeResult.copiedCodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6-2. 카테고리 매핑 + 값 복사
|
|
|
|
|
if (additionalCopyOptions.copyCategoryMapping) {
|
|
|
|
|
logger.info("\n📦 [6-2단계] 카테고리 매핑 + 값 복사");
|
|
|
|
|
copiedCategoryMappings = await this.copyCategoryMappingsAndValues(
|
|
|
|
|
menuObjids,
|
|
|
|
|
menuIdMap,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
userId,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6-3. 테이블 타입관리 입력타입 설정 복사
|
|
|
|
|
if (additionalCopyOptions.copyTableTypeColumns) {
|
|
|
|
|
logger.info("\n📦 [6-3단계] 테이블 타입 설정 복사");
|
|
|
|
|
copiedTableTypeColumns = await this.copyTableTypeColumns(
|
|
|
|
|
Array.from(screenIdMap.keys()), // 원본 화면 IDs
|
|
|
|
|
sourceCompanyCode,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// === 7단계: 테이블 타입 설정 복사 ===
|
|
|
|
|
if (additionalCopyOptions?.copyTableTypeColumns) {
|
|
|
|
|
logger.info("\n📦 [7단계] 테이블 타입 설정 복사");
|
|
|
|
|
copiedTableTypeColumns = await this.copyTableTypeColumns(
|
|
|
|
|
Array.from(screenIdMap.keys()),
|
|
|
|
|
sourceCompanyCode,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
client
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 커밋
|
|
|
|
|
@@ -872,6 +887,7 @@ export class MenuCopyService {
|
|
|
|
|
copiedNumberingRules,
|
|
|
|
|
copiedCategoryMappings,
|
|
|
|
|
copiedTableTypeColumns,
|
|
|
|
|
copiedCascadingRelations,
|
|
|
|
|
menuIdMap: Object.fromEntries(menuIdMap),
|
|
|
|
|
screenIdMap: Object.fromEntries(screenIdMap),
|
|
|
|
|
flowIdMap: Object.fromEntries(flowIdMap),
|
|
|
|
|
@@ -889,6 +905,7 @@ export class MenuCopyService {
|
|
|
|
|
- 채번규칙: ${copiedNumberingRules}개
|
|
|
|
|
- 카테고리 매핑: ${copiedCategoryMappings}개
|
|
|
|
|
- 테이블 타입 설정: ${copiedTableTypeColumns}개
|
|
|
|
|
- 연쇄관계: ${copiedCascadingRelations}개
|
|
|
|
|
============================================
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
@@ -1569,7 +1586,8 @@ export class MenuCopyService {
|
|
|
|
|
targetCompanyCode: string,
|
|
|
|
|
screenIdMap: Map<number, number>,
|
|
|
|
|
userId: string,
|
|
|
|
|
client: PoolClient
|
|
|
|
|
client: PoolClient,
|
|
|
|
|
preAllocatedMenuIdMap?: Map<number, number> // 미리 할당된 메뉴 ID 맵 (옵션 데이터 복사에 사용된 경우)
|
|
|
|
|
): Promise<Map<number, number>> {
|
|
|
|
|
const menuIdMap = new Map<number, number>();
|
|
|
|
|
|
|
|
|
|
@@ -1676,7 +1694,8 @@ export class MenuCopyService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 신규 메뉴 복사 ===
|
|
|
|
|
const newObjId = await this.getNextMenuObjid(client);
|
|
|
|
|
// 미리 할당된 ID가 있으면 사용, 없으면 새로 생성
|
|
|
|
|
const newObjId = preAllocatedMenuIdMap?.get(menu.objid) ?? await this.getNextMenuObjid(client);
|
|
|
|
|
|
|
|
|
|
// source_menu_objid 저장: 모든 복사된 메뉴에 원본 ID 저장 (추적용)
|
|
|
|
|
const sourceMenuObjid = menu.objid;
|
|
|
|
|
@@ -2219,4 +2238,184 @@ export class MenuCopyService {
|
|
|
|
|
return copiedCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 연쇄관계 복사
|
|
|
|
|
* - category_value_cascading_group + category_value_cascading_mapping
|
|
|
|
|
* - cascading_relation (테이블 기반)
|
|
|
|
|
*/
|
|
|
|
|
private async copyCascadingRelations(
|
|
|
|
|
sourceCompanyCode: string,
|
|
|
|
|
targetCompanyCode: string,
|
|
|
|
|
menuIdMap: Map<number, number>,
|
|
|
|
|
userId: string,
|
|
|
|
|
client: PoolClient
|
|
|
|
|
): Promise<number> {
|
|
|
|
|
logger.info(`📋 연쇄관계 복사 시작`);
|
|
|
|
|
let copiedCount = 0;
|
|
|
|
|
|
|
|
|
|
// === 1. category_value_cascading_group 복사 ===
|
|
|
|
|
const groupsResult = await client.query(
|
|
|
|
|
`SELECT * FROM category_value_cascading_group
|
|
|
|
|
WHERE company_code = $1 AND is_active = 'Y'`,
|
|
|
|
|
[sourceCompanyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info(` 카테고리 값 연쇄 그룹: ${groupsResult.rows.length}개`);
|
|
|
|
|
|
|
|
|
|
// group_id 매핑 (매핑 복사 시 사용)
|
|
|
|
|
const groupIdMap = new Map<number, number>();
|
|
|
|
|
|
|
|
|
|
for (const group of groupsResult.rows) {
|
|
|
|
|
// 대상 회사에 같은 relation_code가 있는지 확인
|
|
|
|
|
const existing = await client.query(
|
|
|
|
|
`SELECT group_id FROM category_value_cascading_group
|
|
|
|
|
WHERE relation_code = $1 AND company_code = $2`,
|
|
|
|
|
[group.relation_code, targetCompanyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existing.rows.length > 0) {
|
|
|
|
|
// 이미 존재하면 스킵 (기존 설정 유지)
|
|
|
|
|
groupIdMap.set(group.group_id, existing.rows[0].group_id);
|
|
|
|
|
logger.info(` ↳ ${group.relation_name}: 이미 존재 (스킵)`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// menu_objid 재매핑
|
|
|
|
|
const newParentMenuObjid = group.parent_menu_objid
|
|
|
|
|
? menuIdMap.get(Number(group.parent_menu_objid)) || null
|
|
|
|
|
: null;
|
|
|
|
|
const newChildMenuObjid = group.child_menu_objid
|
|
|
|
|
? menuIdMap.get(Number(group.child_menu_objid)) || null
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
// 새로 삽입
|
|
|
|
|
const insertResult = await client.query(
|
|
|
|
|
`INSERT INTO category_value_cascading_group (
|
|
|
|
|
relation_code, relation_name, description,
|
|
|
|
|
parent_table_name, parent_column_name, parent_menu_objid,
|
|
|
|
|
child_table_name, child_column_name, child_menu_objid,
|
|
|
|
|
clear_on_parent_change, show_group_label,
|
|
|
|
|
empty_parent_message, no_options_message,
|
|
|
|
|
company_code, is_active, created_by, created_date
|
|
|
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, NOW())
|
|
|
|
|
RETURNING group_id`,
|
|
|
|
|
[
|
|
|
|
|
group.relation_code,
|
|
|
|
|
group.relation_name,
|
|
|
|
|
group.description,
|
|
|
|
|
group.parent_table_name,
|
|
|
|
|
group.parent_column_name,
|
|
|
|
|
newParentMenuObjid,
|
|
|
|
|
group.child_table_name,
|
|
|
|
|
group.child_column_name,
|
|
|
|
|
newChildMenuObjid,
|
|
|
|
|
group.clear_on_parent_change,
|
|
|
|
|
group.show_group_label,
|
|
|
|
|
group.empty_parent_message,
|
|
|
|
|
group.no_options_message,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
"Y",
|
|
|
|
|
userId,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const newGroupId = insertResult.rows[0].group_id;
|
|
|
|
|
groupIdMap.set(group.group_id, newGroupId);
|
|
|
|
|
logger.info(` ↳ ${group.relation_name}: 신규 추가 (ID: ${newGroupId})`);
|
|
|
|
|
copiedCount++;
|
|
|
|
|
|
|
|
|
|
// 해당 그룹의 매핑 복사
|
|
|
|
|
const mappingsResult = await client.query(
|
|
|
|
|
`SELECT * FROM category_value_cascading_mapping
|
|
|
|
|
WHERE group_id = $1 AND company_code = $2`,
|
|
|
|
|
[group.group_id, sourceCompanyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const mapping of mappingsResult.rows) {
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO category_value_cascading_mapping (
|
|
|
|
|
group_id, parent_value_code, parent_value_label,
|
|
|
|
|
child_value_code, child_value_label, display_order,
|
|
|
|
|
company_code, is_active, created_date
|
|
|
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
|
|
|
|
|
[
|
|
|
|
|
newGroupId,
|
|
|
|
|
mapping.parent_value_code,
|
|
|
|
|
mapping.parent_value_label,
|
|
|
|
|
mapping.child_value_code,
|
|
|
|
|
mapping.child_value_label,
|
|
|
|
|
mapping.display_order,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
"Y",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mappingsResult.rows.length > 0) {
|
|
|
|
|
logger.info(` ↳ 매핑 ${mappingsResult.rows.length}개 복사`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === 2. cascading_relation 복사 (테이블 기반) ===
|
|
|
|
|
const relationsResult = await client.query(
|
|
|
|
|
`SELECT * FROM cascading_relation
|
|
|
|
|
WHERE company_code = $1 AND is_active = 'Y'`,
|
|
|
|
|
[sourceCompanyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
logger.info(` 기본 연쇄관계: ${relationsResult.rows.length}개`);
|
|
|
|
|
|
|
|
|
|
for (const relation of relationsResult.rows) {
|
|
|
|
|
// 대상 회사에 같은 relation_code가 있는지 확인
|
|
|
|
|
const existing = await client.query(
|
|
|
|
|
`SELECT relation_id FROM cascading_relation
|
|
|
|
|
WHERE relation_code = $1 AND company_code = $2`,
|
|
|
|
|
[relation.relation_code, targetCompanyCode]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existing.rows.length > 0) {
|
|
|
|
|
logger.info(` ↳ ${relation.relation_name}: 이미 존재 (스킵)`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 새로 삽입
|
|
|
|
|
await client.query(
|
|
|
|
|
`INSERT INTO cascading_relation (
|
|
|
|
|
relation_code, relation_name, description,
|
|
|
|
|
parent_table, parent_value_column, parent_label_column,
|
|
|
|
|
child_table, child_filter_column, child_value_column, child_label_column,
|
|
|
|
|
child_order_column, child_order_direction,
|
|
|
|
|
empty_parent_message, no_options_message, loading_message,
|
|
|
|
|
clear_on_parent_change, company_code, is_active, created_by, created_date
|
|
|
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, NOW())`,
|
|
|
|
|
[
|
|
|
|
|
relation.relation_code,
|
|
|
|
|
relation.relation_name,
|
|
|
|
|
relation.description,
|
|
|
|
|
relation.parent_table,
|
|
|
|
|
relation.parent_value_column,
|
|
|
|
|
relation.parent_label_column,
|
|
|
|
|
relation.child_table,
|
|
|
|
|
relation.child_filter_column,
|
|
|
|
|
relation.child_value_column,
|
|
|
|
|
relation.child_label_column,
|
|
|
|
|
relation.child_order_column,
|
|
|
|
|
relation.child_order_direction,
|
|
|
|
|
relation.empty_parent_message,
|
|
|
|
|
relation.no_options_message,
|
|
|
|
|
relation.loading_message,
|
|
|
|
|
relation.clear_on_parent_change,
|
|
|
|
|
targetCompanyCode,
|
|
|
|
|
"Y",
|
|
|
|
|
userId,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
logger.info(` ↳ ${relation.relation_name}: 신규 추가`);
|
|
|
|
|
copiedCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info(`✅ 연쇄관계 복사 완료: ${copiedCount}개`);
|
|
|
|
|
return copiedCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|