feat: V2 레이아웃 처리 개선 및 새로운 V2 레이아웃 데이터 구조 도입
- 기존 레이아웃 처리 로직을 V2 레이아웃에 맞게 수정하였습니다. - V2 레이아웃에서 layout_data를 조회하고, 변경 여부를 확인하는 로직을 추가하였습니다. - 레이아웃 데이터의 참조 ID 업데이트 및 flowId, numberingRuleId 수집 기능을 구현하였습니다. - V2Media 컴포넌트를 통합하여 미디어 관련 기능을 강화하였습니다. - 레이아웃 처리 시 V2 레이아웃의 컴포넌트 매핑 및 데이터 복사를 효율적으로 처리하도록 개선하였습니다.
This commit is contained in:
@@ -1556,22 +1556,22 @@ export class MenuCopyService {
|
||||
// === 기존 복사본이 있는 경우: 업데이트 ===
|
||||
const existingScreenId = existingCopy.screen_id;
|
||||
|
||||
// 원본 레이아웃 조회
|
||||
const sourceLayoutsResult = await client.query<ScreenLayout>(
|
||||
`SELECT * FROM screen_layouts WHERE screen_id = $1 ORDER BY display_order`,
|
||||
// 원본 V2 레이아웃 조회
|
||||
const sourceLayoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2 WHERE screen_id = $1`,
|
||||
[originalScreenId]
|
||||
);
|
||||
|
||||
// 대상 레이아웃 조회
|
||||
const targetLayoutsResult = await client.query<ScreenLayout>(
|
||||
`SELECT * FROM screen_layouts WHERE screen_id = $1 ORDER BY display_order`,
|
||||
// 대상 V2 레이아웃 조회
|
||||
const targetLayoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2 WHERE screen_id = $1`,
|
||||
[existingScreenId]
|
||||
);
|
||||
|
||||
// 변경 여부 확인 (레이아웃 개수 또는 내용 비교)
|
||||
const hasChanges = this.hasLayoutChanges(
|
||||
sourceLayoutsResult.rows,
|
||||
targetLayoutsResult.rows
|
||||
// 변경 여부 확인 (V2 레이아웃 비교)
|
||||
const hasChanges = this.hasLayoutChangesV2(
|
||||
sourceLayoutV2Result.rows[0]?.layout_data,
|
||||
targetLayoutV2Result.rows[0]?.layout_data
|
||||
);
|
||||
|
||||
if (hasChanges) {
|
||||
@@ -1673,9 +1673,9 @@ export class MenuCopyService {
|
||||
}
|
||||
}
|
||||
|
||||
// === 2단계: screen_layouts 처리 (이제 screenIdMap이 완성됨) ===
|
||||
// === 2단계: screen_layouts_v2 처리 (이제 screenIdMap이 완성됨) ===
|
||||
logger.info(
|
||||
`\n📐 레이아웃 처리 시작 (screenIdMap 완성: ${screenIdMap.size}개)`
|
||||
`\n📐 V2 레이아웃 처리 시작 (screenIdMap 완성: ${screenIdMap.size}개)`
|
||||
);
|
||||
|
||||
for (const {
|
||||
@@ -1685,91 +1685,51 @@ export class MenuCopyService {
|
||||
isUpdate,
|
||||
} of screenDefsToProcess) {
|
||||
try {
|
||||
// 원본 레이아웃 조회
|
||||
const layoutsResult = await client.query<ScreenLayout>(
|
||||
`SELECT * FROM screen_layouts WHERE screen_id = $1 ORDER BY display_order`,
|
||||
// 원본 V2 레이아웃 조회
|
||||
const layoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2 WHERE screen_id = $1`,
|
||||
[originalScreenId]
|
||||
);
|
||||
|
||||
if (isUpdate) {
|
||||
// 업데이트: 기존 레이아웃 삭제 후 새로 삽입
|
||||
await client.query(
|
||||
`DELETE FROM screen_layouts WHERE screen_id = $1`,
|
||||
[targetScreenId]
|
||||
const layoutData = layoutV2Result.rows[0]?.layout_data;
|
||||
const components = layoutData?.components || [];
|
||||
|
||||
if (layoutData && components.length > 0) {
|
||||
// component_id 매핑 생성 (원본 → 새 ID)
|
||||
const componentIdMap = new Map<string, string>();
|
||||
const timestamp = Date.now();
|
||||
components.forEach((comp: any, idx: number) => {
|
||||
const newComponentId = `comp_${timestamp}_${idx}_${Math.random().toString(36).substr(2, 5)}`;
|
||||
componentIdMap.set(comp.id, newComponentId);
|
||||
});
|
||||
|
||||
// V2 레이아웃 데이터 복사 및 참조 업데이트
|
||||
const updatedLayoutData = this.updateReferencesInLayoutDataV2(
|
||||
layoutData,
|
||||
componentIdMap,
|
||||
screenIdMap,
|
||||
flowIdMap,
|
||||
numberingRuleIdMap,
|
||||
menuIdMap
|
||||
);
|
||||
logger.info(` ↳ 기존 레이아웃 삭제 (업데이트 준비)`);
|
||||
}
|
||||
|
||||
// component_id 매핑 생성 (원본 → 새 ID)
|
||||
const componentIdMap = new Map<string, string>();
|
||||
const timestamp = Date.now();
|
||||
layoutsResult.rows.forEach((layout, idx) => {
|
||||
const newComponentId = `comp_${timestamp}_${idx}_${Math.random().toString(36).substr(2, 5)}`;
|
||||
componentIdMap.set(layout.component_id, newComponentId);
|
||||
});
|
||||
|
||||
// 레이아웃 배치 삽입 준비
|
||||
if (layoutsResult.rows.length > 0) {
|
||||
const layoutValues: string[] = [];
|
||||
const layoutParams: any[] = [];
|
||||
let paramIdx = 1;
|
||||
|
||||
for (const layout of layoutsResult.rows) {
|
||||
const newComponentId = componentIdMap.get(layout.component_id)!;
|
||||
|
||||
const newParentId = layout.parent_id
|
||||
? componentIdMap.get(layout.parent_id) || layout.parent_id
|
||||
: null;
|
||||
const newZoneId = layout.zone_id
|
||||
? componentIdMap.get(layout.zone_id) || layout.zone_id
|
||||
: null;
|
||||
|
||||
const updatedProperties = this.updateReferencesInProperties(
|
||||
layout.properties,
|
||||
screenIdMap,
|
||||
flowIdMap,
|
||||
numberingRuleIdMap,
|
||||
menuIdMap
|
||||
);
|
||||
|
||||
layoutValues.push(
|
||||
`($${paramIdx}, $${paramIdx + 1}, $${paramIdx + 2}, $${paramIdx + 3}, $${paramIdx + 4}, $${paramIdx + 5}, $${paramIdx + 6}, $${paramIdx + 7}, $${paramIdx + 8}, $${paramIdx + 9}, $${paramIdx + 10}, $${paramIdx + 11}, $${paramIdx + 12}, $${paramIdx + 13})`
|
||||
);
|
||||
layoutParams.push(
|
||||
targetScreenId,
|
||||
layout.component_type,
|
||||
newComponentId,
|
||||
newParentId,
|
||||
layout.position_x,
|
||||
layout.position_y,
|
||||
layout.width,
|
||||
layout.height,
|
||||
updatedProperties,
|
||||
layout.display_order,
|
||||
layout.layout_type,
|
||||
layout.layout_config,
|
||||
layout.zones_config,
|
||||
newZoneId
|
||||
);
|
||||
paramIdx += 14;
|
||||
}
|
||||
|
||||
// 배치 INSERT
|
||||
// V2 레이아웃 저장 (UPSERT)
|
||||
await client.query(
|
||||
`INSERT INTO screen_layouts (
|
||||
screen_id, component_type, component_id, parent_id,
|
||||
position_x, position_y, width, height, properties,
|
||||
display_order, layout_type, layout_config, zones_config, zone_id
|
||||
) VALUES ${layoutValues.join(", ")}`,
|
||||
layoutParams
|
||||
`INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, NOW(), NOW())
|
||||
ON CONFLICT (screen_id, company_code)
|
||||
DO UPDATE SET layout_data = $3, updated_at = NOW()`,
|
||||
[targetScreenId, targetCompanyCode, JSON.stringify(updatedLayoutData)]
|
||||
);
|
||||
}
|
||||
|
||||
const action = isUpdate ? "업데이트" : "복사";
|
||||
logger.info(` ↳ 레이아웃 ${action}: ${layoutsResult.rows.length}개`);
|
||||
const action = isUpdate ? "업데이트" : "복사";
|
||||
logger.info(` ↳ V2 레이아웃 ${action}: ${components.length}개 컴포넌트`);
|
||||
} else {
|
||||
logger.info(` ↳ V2 레이아웃 없음 (스킵): screen_id=${originalScreenId}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`❌ 레이아웃 처리 실패: screen_id=${originalScreenId}`,
|
||||
`❌ V2 레이아웃 처리 실패: screen_id=${originalScreenId}`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
@@ -1835,6 +1795,83 @@ export class MenuCopyService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃 변경 여부 확인 (screen_layouts_v2용)
|
||||
*/
|
||||
private hasLayoutChangesV2(
|
||||
sourceLayoutData: any,
|
||||
targetLayoutData: any
|
||||
): boolean {
|
||||
// 1. 둘 다 없으면 변경 없음
|
||||
if (!sourceLayoutData && !targetLayoutData) return false;
|
||||
|
||||
// 2. 하나만 있으면 변경됨
|
||||
if (!sourceLayoutData || !targetLayoutData) return true;
|
||||
|
||||
// 3. components 배열 비교
|
||||
const sourceComps = sourceLayoutData.components || [];
|
||||
const targetComps = targetLayoutData.components || [];
|
||||
|
||||
if (sourceComps.length !== targetComps.length) return true;
|
||||
|
||||
// 4. 각 컴포넌트 비교 (url, position, size, overrides)
|
||||
for (let i = 0; i < sourceComps.length; i++) {
|
||||
const s = sourceComps[i];
|
||||
const t = targetComps[i];
|
||||
|
||||
if (s.url !== t.url) return true;
|
||||
if (JSON.stringify(s.position) !== JSON.stringify(t.position)) return true;
|
||||
if (JSON.stringify(s.size) !== JSON.stringify(t.size)) return true;
|
||||
if (JSON.stringify(s.overrides) !== JSON.stringify(t.overrides)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃 데이터의 참조 ID들을 업데이트 (componentId, flowId, ruleId, screenId, menuId)
|
||||
*/
|
||||
private updateReferencesInLayoutDataV2(
|
||||
layoutData: any,
|
||||
componentIdMap: Map<string, string>,
|
||||
screenIdMap: Map<number, number>,
|
||||
flowIdMap: Map<number, number>,
|
||||
numberingRuleIdMap?: Map<string, string>,
|
||||
menuIdMap?: Map<number, number>
|
||||
): any {
|
||||
if (!layoutData?.components) return layoutData;
|
||||
|
||||
const updatedComponents = layoutData.components.map((comp: any) => {
|
||||
// 1. componentId 매핑
|
||||
const newId = componentIdMap.get(comp.id) || comp.id;
|
||||
|
||||
// 2. overrides 복사 및 재귀적 참조 업데이트
|
||||
let overrides = JSON.parse(JSON.stringify(comp.overrides || {}));
|
||||
|
||||
// 재귀적으로 모든 참조 업데이트
|
||||
this.recursiveUpdateReferences(
|
||||
overrides,
|
||||
screenIdMap,
|
||||
flowIdMap,
|
||||
"",
|
||||
numberingRuleIdMap,
|
||||
menuIdMap
|
||||
);
|
||||
|
||||
return {
|
||||
...comp,
|
||||
id: newId,
|
||||
overrides,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...layoutData,
|
||||
components: updatedComponents,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 위상 정렬 (부모 먼저)
|
||||
*/
|
||||
|
||||
@@ -3481,6 +3481,371 @@ export class ScreenManagementService {
|
||||
return flowIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃에서 flowId 수집 (screen_layouts_v2용)
|
||||
* - overrides.flowId (flow-widget)
|
||||
* - overrides.webTypeConfig.dataflowConfig.flowConfig.flowId (버튼)
|
||||
* - overrides.webTypeConfig.dataflowConfig.flowControls[].flowId
|
||||
* - overrides.action.excelAfterUploadFlows[].flowId
|
||||
*/
|
||||
private collectFlowIdsFromLayoutData(layoutData: any): Set<number> {
|
||||
const flowIds = new Set<number>();
|
||||
if (!layoutData?.components) return flowIds;
|
||||
|
||||
for (const comp of layoutData.components) {
|
||||
const overrides = comp.overrides || {};
|
||||
|
||||
// 1. overrides.flowId (flow-widget 등)
|
||||
if (overrides.flowId && !isNaN(parseInt(overrides.flowId))) {
|
||||
flowIds.add(parseInt(overrides.flowId));
|
||||
}
|
||||
|
||||
// 2. webTypeConfig.dataflowConfig.flowConfig.flowId (버튼)
|
||||
const flowConfigId = overrides?.webTypeConfig?.dataflowConfig?.flowConfig?.flowId;
|
||||
if (flowConfigId && !isNaN(parseInt(flowConfigId))) {
|
||||
flowIds.add(parseInt(flowConfigId));
|
||||
}
|
||||
|
||||
// 3. webTypeConfig.dataflowConfig.selectedDiagramId
|
||||
const diagramId = overrides?.webTypeConfig?.dataflowConfig?.selectedDiagramId;
|
||||
if (diagramId && !isNaN(parseInt(diagramId))) {
|
||||
flowIds.add(parseInt(diagramId));
|
||||
}
|
||||
|
||||
// 4. webTypeConfig.dataflowConfig.flowControls[].flowId
|
||||
const flowControls = overrides?.webTypeConfig?.dataflowConfig?.flowControls;
|
||||
if (Array.isArray(flowControls)) {
|
||||
for (const control of flowControls) {
|
||||
if (control?.flowId && !isNaN(parseInt(control.flowId))) {
|
||||
flowIds.add(parseInt(control.flowId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. action.excelAfterUploadFlows[].flowId
|
||||
const excelFlows = overrides?.action?.excelAfterUploadFlows;
|
||||
if (Array.isArray(excelFlows)) {
|
||||
for (const flow of excelFlows) {
|
||||
if (flow?.flowId && !isNaN(parseInt(flow.flowId))) {
|
||||
flowIds.add(parseInt(flow.flowId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flowIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃에서 numberingRuleId 수집 (screen_layouts_v2용)
|
||||
* - overrides.autoGeneration.options.numberingRuleId
|
||||
* - overrides.sections[].fields[].numberingRule.ruleId
|
||||
* - overrides.action.excelNumberingRuleId
|
||||
* - overrides.action.numberingRuleId
|
||||
*/
|
||||
private collectNumberingRuleIdsFromLayoutData(layoutData: any): Set<string> {
|
||||
const ruleIds = new Set<string>();
|
||||
if (!layoutData?.components) return ruleIds;
|
||||
|
||||
for (const comp of layoutData.components) {
|
||||
const overrides = comp.overrides || {};
|
||||
|
||||
// 1. autoGeneration.options.numberingRuleId
|
||||
const autoGenRuleId = overrides?.autoGeneration?.options?.numberingRuleId;
|
||||
if (autoGenRuleId && typeof autoGenRuleId === "string" && autoGenRuleId.startsWith("rule-")) {
|
||||
ruleIds.add(autoGenRuleId);
|
||||
}
|
||||
|
||||
// 2. sections[].fields[].numberingRule.ruleId
|
||||
const sections = overrides?.sections;
|
||||
if (Array.isArray(sections)) {
|
||||
for (const section of sections) {
|
||||
const fields = section?.fields;
|
||||
if (Array.isArray(fields)) {
|
||||
for (const field of fields) {
|
||||
const ruleId = field?.numberingRule?.ruleId;
|
||||
if (ruleId && typeof ruleId === "string" && ruleId.startsWith("rule-")) {
|
||||
ruleIds.add(ruleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// optionalFieldGroups 내부
|
||||
const optGroups = section?.optionalFieldGroups;
|
||||
if (Array.isArray(optGroups)) {
|
||||
for (const optGroup of optGroups) {
|
||||
const optFields = optGroup?.fields;
|
||||
if (Array.isArray(optFields)) {
|
||||
for (const field of optFields) {
|
||||
const ruleId = field?.numberingRule?.ruleId;
|
||||
if (ruleId && typeof ruleId === "string" && ruleId.startsWith("rule-")) {
|
||||
ruleIds.add(ruleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. action.excelNumberingRuleId
|
||||
const excelRuleId = overrides?.action?.excelNumberingRuleId;
|
||||
if (excelRuleId && typeof excelRuleId === "string" && excelRuleId.startsWith("rule-")) {
|
||||
ruleIds.add(excelRuleId);
|
||||
}
|
||||
|
||||
// 4. action.numberingRuleId
|
||||
const actionRuleId = overrides?.action?.numberingRuleId;
|
||||
if (actionRuleId && typeof actionRuleId === "string" && actionRuleId.startsWith("rule-")) {
|
||||
ruleIds.add(actionRuleId);
|
||||
}
|
||||
}
|
||||
|
||||
return ruleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃 데이터의 참조 ID들을 업데이트
|
||||
* - componentId, flowId, numberingRuleId, screenId 매핑 적용
|
||||
*/
|
||||
private updateReferencesInLayoutData(
|
||||
layoutData: any,
|
||||
mappings: {
|
||||
componentIdMap: Map<string, string>;
|
||||
flowIdMap?: Map<number, number>;
|
||||
ruleIdMap?: Map<string, string>;
|
||||
screenIdMap?: Map<number, number>;
|
||||
},
|
||||
): any {
|
||||
if (!layoutData?.components) return layoutData;
|
||||
|
||||
const updatedComponents = layoutData.components.map((comp: any) => {
|
||||
// 1. componentId 매핑
|
||||
const newId = mappings.componentIdMap.get(comp.id) || comp.id;
|
||||
|
||||
// 2. overrides 복사 및 참조 업데이트
|
||||
let overrides = JSON.parse(JSON.stringify(comp.overrides || {}));
|
||||
|
||||
// flowId 매핑
|
||||
if (mappings.flowIdMap && mappings.flowIdMap.size > 0) {
|
||||
overrides = this.updateFlowIdsInOverrides(overrides, mappings.flowIdMap);
|
||||
}
|
||||
|
||||
// numberingRuleId 매핑
|
||||
if (mappings.ruleIdMap && mappings.ruleIdMap.size > 0) {
|
||||
overrides = this.updateNumberingRuleIdsInOverrides(overrides, mappings.ruleIdMap);
|
||||
}
|
||||
|
||||
// screenId 매핑 (탭, 버튼 등)
|
||||
if (mappings.screenIdMap && mappings.screenIdMap.size > 0) {
|
||||
overrides = this.updateScreenIdsInOverrides(overrides, mappings.screenIdMap);
|
||||
}
|
||||
|
||||
return {
|
||||
...comp,
|
||||
id: newId,
|
||||
overrides,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...layoutData,
|
||||
components: updatedComponents,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 overrides 내의 flowId 업데이트
|
||||
*/
|
||||
private updateFlowIdsInOverrides(
|
||||
overrides: any,
|
||||
flowIdMap: Map<number, number>,
|
||||
): any {
|
||||
if (!overrides || flowIdMap.size === 0) return overrides;
|
||||
|
||||
// 1. overrides.flowId (flow-widget)
|
||||
if (overrides.flowId) {
|
||||
const oldId = parseInt(overrides.flowId);
|
||||
const newId = flowIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.flowId = newId;
|
||||
console.log(` 🔗 flowId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. webTypeConfig.dataflowConfig.flowConfig.flowId
|
||||
if (overrides?.webTypeConfig?.dataflowConfig?.flowConfig?.flowId) {
|
||||
const oldId = parseInt(overrides.webTypeConfig.dataflowConfig.flowConfig.flowId);
|
||||
const newId = flowIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.webTypeConfig.dataflowConfig.flowConfig.flowId = newId;
|
||||
console.log(` 🔗 flowConfig.flowId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. webTypeConfig.dataflowConfig.selectedDiagramId
|
||||
if (overrides?.webTypeConfig?.dataflowConfig?.selectedDiagramId) {
|
||||
const oldId = parseInt(overrides.webTypeConfig.dataflowConfig.selectedDiagramId);
|
||||
const newId = flowIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.webTypeConfig.dataflowConfig.selectedDiagramId = newId;
|
||||
console.log(` 🔗 selectedDiagramId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. webTypeConfig.dataflowConfig.flowControls[]
|
||||
if (Array.isArray(overrides?.webTypeConfig?.dataflowConfig?.flowControls)) {
|
||||
for (const control of overrides.webTypeConfig.dataflowConfig.flowControls) {
|
||||
if (control?.flowId) {
|
||||
const oldId = parseInt(control.flowId);
|
||||
const newId = flowIdMap.get(oldId);
|
||||
if (newId) {
|
||||
control.flowId = newId;
|
||||
console.log(` 🔗 flowControls.flowId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. action.excelAfterUploadFlows[]
|
||||
if (Array.isArray(overrides?.action?.excelAfterUploadFlows)) {
|
||||
for (const flow of overrides.action.excelAfterUploadFlows) {
|
||||
if (flow?.flowId) {
|
||||
const oldId = parseInt(flow.flowId);
|
||||
const newId = flowIdMap.get(oldId);
|
||||
if (newId) {
|
||||
flow.flowId = newId;
|
||||
console.log(` 🔗 excelAfterUploadFlows.flowId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 overrides 내의 numberingRuleId 업데이트
|
||||
*/
|
||||
private updateNumberingRuleIdsInOverrides(
|
||||
overrides: any,
|
||||
ruleIdMap: Map<string, string>,
|
||||
): any {
|
||||
if (!overrides || ruleIdMap.size === 0) return overrides;
|
||||
|
||||
// 1. autoGeneration.options.numberingRuleId
|
||||
if (overrides?.autoGeneration?.options?.numberingRuleId) {
|
||||
const oldId = overrides.autoGeneration.options.numberingRuleId;
|
||||
const newId = ruleIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.autoGeneration.options.numberingRuleId = newId;
|
||||
console.log(` 🔗 autoGeneration.numberingRuleId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. sections[].fields[].numberingRule.ruleId
|
||||
if (Array.isArray(overrides?.sections)) {
|
||||
for (const section of overrides.sections) {
|
||||
if (Array.isArray(section?.fields)) {
|
||||
for (const field of section.fields) {
|
||||
if (field?.numberingRule?.ruleId) {
|
||||
const oldId = field.numberingRule.ruleId;
|
||||
const newId = ruleIdMap.get(oldId);
|
||||
if (newId) {
|
||||
field.numberingRule.ruleId = newId;
|
||||
console.log(` 🔗 field.numberingRule.ruleId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(section?.optionalFieldGroups)) {
|
||||
for (const optGroup of section.optionalFieldGroups) {
|
||||
if (Array.isArray(optGroup?.fields)) {
|
||||
for (const field of optGroup.fields) {
|
||||
if (field?.numberingRule?.ruleId) {
|
||||
const oldId = field.numberingRule.ruleId;
|
||||
const newId = ruleIdMap.get(oldId);
|
||||
if (newId) {
|
||||
field.numberingRule.ruleId = newId;
|
||||
console.log(` 🔗 optField.numberingRule.ruleId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. action.excelNumberingRuleId
|
||||
if (overrides?.action?.excelNumberingRuleId) {
|
||||
const oldId = overrides.action.excelNumberingRuleId;
|
||||
const newId = ruleIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.action.excelNumberingRuleId = newId;
|
||||
console.log(` 🔗 excelNumberingRuleId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. action.numberingRuleId
|
||||
if (overrides?.action?.numberingRuleId) {
|
||||
const oldId = overrides.action.numberingRuleId;
|
||||
const newId = ruleIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.action.numberingRuleId = newId;
|
||||
console.log(` 🔗 action.numberingRuleId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 overrides 내의 screenId 업데이트 (탭, 버튼 등)
|
||||
*/
|
||||
private updateScreenIdsInOverrides(
|
||||
overrides: any,
|
||||
screenIdMap: Map<number, number>,
|
||||
): any {
|
||||
if (!overrides || screenIdMap.size === 0) return overrides;
|
||||
|
||||
// 1. tabs[].screenId (탭 위젯)
|
||||
if (Array.isArray(overrides?.tabs)) {
|
||||
for (const tab of overrides.tabs) {
|
||||
if (tab?.screenId) {
|
||||
const oldId = parseInt(tab.screenId);
|
||||
const newId = screenIdMap.get(oldId);
|
||||
if (newId) {
|
||||
tab.screenId = newId;
|
||||
console.log(` 🔗 tab.screenId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. action.targetScreenId (버튼)
|
||||
if (overrides?.action?.targetScreenId) {
|
||||
const oldId = parseInt(overrides.action.targetScreenId);
|
||||
const newId = screenIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.action.targetScreenId = newId;
|
||||
console.log(` 🔗 action.targetScreenId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. action.modalScreenId
|
||||
if (overrides?.action?.modalScreenId) {
|
||||
const oldId = parseInt(overrides.action.modalScreenId);
|
||||
const newId = screenIdMap.get(oldId);
|
||||
if (newId) {
|
||||
overrides.action.modalScreenId = newId;
|
||||
console.log(` 🔗 action.modalScreenId: ${oldId} → ${newId}`);
|
||||
}
|
||||
}
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
/**
|
||||
* 노드 플로우 복사 및 ID 매핑 반환
|
||||
* - 원본 회사의 플로우를 대상 회사로 복사
|
||||
@@ -3719,24 +4084,34 @@ export class ScreenManagementService {
|
||||
|
||||
const newScreen = newScreenResult.rows[0];
|
||||
|
||||
// 4. 원본 화면의 레이아웃 정보 조회
|
||||
const sourceLayoutsResult = await client.query<any>(
|
||||
`SELECT * FROM screen_layouts
|
||||
WHERE screen_id = $1
|
||||
ORDER BY display_order ASC NULLS LAST`,
|
||||
[sourceScreenId],
|
||||
// 4. 원본 화면의 V2 레이아웃 조회
|
||||
let sourceLayoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2
|
||||
WHERE screen_id = $1 AND company_code = $2`,
|
||||
[sourceScreenId, sourceScreen.company_code],
|
||||
);
|
||||
|
||||
const sourceLayouts = sourceLayoutsResult.rows;
|
||||
// 없으면 공통(*) 레이아웃 조회
|
||||
let layoutData = sourceLayoutV2Result.rows[0]?.layout_data;
|
||||
if (!layoutData && sourceScreen.company_code !== "*") {
|
||||
const fallbackResult = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2
|
||||
WHERE screen_id = $1 AND company_code = '*'`,
|
||||
[sourceScreenId],
|
||||
);
|
||||
layoutData = fallbackResult.rows[0]?.layout_data;
|
||||
}
|
||||
|
||||
const components = layoutData?.components || [];
|
||||
|
||||
// 5. 노드 플로우 복사 (회사가 다른 경우)
|
||||
let flowIdMap = new Map<number, number>();
|
||||
if (
|
||||
sourceLayouts.length > 0 &&
|
||||
components.length > 0 &&
|
||||
sourceScreen.company_code !== targetCompanyCode
|
||||
) {
|
||||
// 레이아웃에서 사용하는 flowId 수집
|
||||
const flowIds = this.collectFlowIdsFromLayouts(sourceLayouts);
|
||||
// V2 레이아웃에서 flowId 수집
|
||||
const flowIds = this.collectFlowIdsFromLayoutData(layoutData);
|
||||
|
||||
if (flowIds.size > 0) {
|
||||
console.log(`🔍 화면 복사 - flowId 수집: ${flowIds.size}개`);
|
||||
@@ -3754,11 +4129,11 @@ export class ScreenManagementService {
|
||||
// 5.1. 채번 규칙 복사 (회사가 다른 경우)
|
||||
let ruleIdMap = new Map<string, string>();
|
||||
if (
|
||||
sourceLayouts.length > 0 &&
|
||||
components.length > 0 &&
|
||||
sourceScreen.company_code !== targetCompanyCode
|
||||
) {
|
||||
// 레이아웃에서 사용하는 채번 규칙 ID 수집
|
||||
const ruleIds = this.collectNumberingRuleIdsFromLayouts(sourceLayouts);
|
||||
// V2 레이아웃에서 채번 규칙 ID 수집
|
||||
const ruleIds = this.collectNumberingRuleIdsFromLayoutData(layoutData);
|
||||
|
||||
if (ruleIds.size > 0) {
|
||||
console.log(`🔍 화면 복사 - 채번 규칙 ID 수집: ${ruleIds.size}개`);
|
||||
@@ -3773,81 +4148,43 @@ export class ScreenManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 레이아웃이 있다면 복사
|
||||
if (sourceLayouts.length > 0) {
|
||||
// 6. V2 레이아웃이 있다면 복사
|
||||
if (layoutData && components.length > 0) {
|
||||
try {
|
||||
// ID 매핑 맵 생성
|
||||
const idMapping: { [oldId: string]: string } = {};
|
||||
|
||||
// 새로운 컴포넌트 ID 미리 생성
|
||||
sourceLayouts.forEach((layout: any) => {
|
||||
idMapping[layout.component_id] = generateId();
|
||||
});
|
||||
|
||||
// 각 레이아웃 컴포넌트 복사
|
||||
for (const sourceLayout of sourceLayouts) {
|
||||
const newComponentId = idMapping[sourceLayout.component_id];
|
||||
const newParentId = sourceLayout.parent_id
|
||||
? idMapping[sourceLayout.parent_id]
|
||||
: null;
|
||||
|
||||
// properties 파싱
|
||||
let properties = sourceLayout.properties;
|
||||
if (typeof properties === "string") {
|
||||
try {
|
||||
properties = JSON.parse(properties);
|
||||
} catch (e) {
|
||||
// 파싱 실패 시 그대로 사용
|
||||
}
|
||||
}
|
||||
|
||||
// flowId 매핑 적용 (회사가 다른 경우)
|
||||
if (flowIdMap.size > 0) {
|
||||
properties = this.updateFlowIdsInProperties(
|
||||
properties,
|
||||
flowIdMap,
|
||||
);
|
||||
}
|
||||
|
||||
// 채번 규칙 ID 매핑 적용 (회사가 다른 경우)
|
||||
if (ruleIdMap.size > 0) {
|
||||
properties = this.updateNumberingRuleIdsInProperties(
|
||||
properties,
|
||||
ruleIdMap,
|
||||
);
|
||||
}
|
||||
|
||||
// 탭 컴포넌트의 screenId는 개별 복제 시점에 업데이트하지 않음
|
||||
// 모든 화면 복제 완료 후 updateTabScreenReferences에서 screenIdMap 기반으로 일괄 업데이트
|
||||
|
||||
await client.query(
|
||||
`INSERT INTO screen_layouts (
|
||||
screen_id, component_type, component_id, parent_id,
|
||||
position_x, position_y, width, height, properties,
|
||||
display_order, created_date
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
|
||||
[
|
||||
newScreen.screen_id,
|
||||
sourceLayout.component_type,
|
||||
newComponentId,
|
||||
newParentId,
|
||||
Math.round(sourceLayout.position_x), // 정수로 반올림
|
||||
Math.round(sourceLayout.position_y), // 정수로 반올림
|
||||
Math.round(sourceLayout.width), // 정수로 반올림
|
||||
Math.round(sourceLayout.height), // 정수로 반올림
|
||||
JSON.stringify(properties),
|
||||
sourceLayout.display_order,
|
||||
new Date(),
|
||||
],
|
||||
);
|
||||
// componentId 매핑 생성
|
||||
const componentIdMap = new Map<string, string>();
|
||||
for (const comp of components) {
|
||||
componentIdMap.set(comp.id, generateId());
|
||||
}
|
||||
|
||||
// V2 레이아웃 데이터 복사 및 참조 업데이트
|
||||
const updatedLayoutData = this.updateReferencesInLayoutData(
|
||||
layoutData,
|
||||
{
|
||||
componentIdMap,
|
||||
flowIdMap: flowIdMap.size > 0 ? flowIdMap : undefined,
|
||||
ruleIdMap: ruleIdMap.size > 0 ? ruleIdMap : undefined,
|
||||
// screenIdMap은 모든 화면 복제 완료 후 updateTabScreenReferences에서 일괄 처리
|
||||
},
|
||||
);
|
||||
|
||||
// V2 레이아웃 저장 (UPSERT)
|
||||
await client.query(
|
||||
`INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, NOW(), NOW())
|
||||
ON CONFLICT (screen_id, company_code)
|
||||
DO UPDATE SET layout_data = $3, updated_at = NOW()`,
|
||||
[newScreen.screen_id, targetCompanyCode, JSON.stringify(updatedLayoutData)],
|
||||
);
|
||||
|
||||
console.log(` ✅ V2 레이아웃 복사 완료: ${components.length}개 컴포넌트`);
|
||||
} catch (error) {
|
||||
console.error("레이아웃 복사 중 오류:", error);
|
||||
console.error("V2 레이아웃 복사 중 오류:", error);
|
||||
// 레이아웃 복사 실패해도 화면 생성은 유지
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 생성된 화면 정보 반환
|
||||
// 7. 생성된 화면 정보 반환
|
||||
return {
|
||||
screenId: newScreen.screen_id,
|
||||
screenCode: newScreen.screen_code,
|
||||
|
||||
Reference in New Issue
Block a user