From 3c73c202927f7facc41f728564ab2b72e5872425 Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 15 Dec 2025 14:51:41 +0900 Subject: [PATCH] =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B3=B5=EC=82=AC?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/routes/cascadingAutoFillRoutes.ts | 1 + .../src/routes/cascadingConditionRoutes.ts | 1 + .../src/routes/cascadingHierarchyRoutes.ts | 1 + .../routes/cascadingMutualExclusionRoutes.ts | 1 + backend-node/src/services/menuCopyService.ts | 79 ++++++++++++++++--- docs/노드플로우_개선사항.md | 1 + docs/메일발송_기능_사용_가이드.md | 1 + frontend/hooks/useAutoFill.ts | 1 + ..._임베딩_및_데이터_전달_시스템_구현_계획서.md | 1 + 화면_임베딩_시스템_Phase1-4_구현_완료.md | 1 + 화면_임베딩_시스템_충돌_분석_보고서.md | 1 + 11 files changed, 77 insertions(+), 12 deletions(-) diff --git a/backend-node/src/routes/cascadingAutoFillRoutes.ts b/backend-node/src/routes/cascadingAutoFillRoutes.ts index de4eb913..7aa1d825 100644 --- a/backend-node/src/routes/cascadingAutoFillRoutes.ts +++ b/backend-node/src/routes/cascadingAutoFillRoutes.ts @@ -51,3 +51,4 @@ router.get("/data/:groupCode", getAutoFillData); export default router; + diff --git a/backend-node/src/routes/cascadingConditionRoutes.ts b/backend-node/src/routes/cascadingConditionRoutes.ts index c2f12782..5f57c6ca 100644 --- a/backend-node/src/routes/cascadingConditionRoutes.ts +++ b/backend-node/src/routes/cascadingConditionRoutes.ts @@ -47,3 +47,4 @@ router.get("/filtered-options/:relationCode", getFilteredOptions); export default router; + diff --git a/backend-node/src/routes/cascadingHierarchyRoutes.ts b/backend-node/src/routes/cascadingHierarchyRoutes.ts index 71e6c418..b0e3c79a 100644 --- a/backend-node/src/routes/cascadingHierarchyRoutes.ts +++ b/backend-node/src/routes/cascadingHierarchyRoutes.ts @@ -63,3 +63,4 @@ router.get("/:groupCode/options/:levelOrder", getLevelOptions); export default router; + diff --git a/backend-node/src/routes/cascadingMutualExclusionRoutes.ts b/backend-node/src/routes/cascadingMutualExclusionRoutes.ts index d92d7d72..0cec35d2 100644 --- a/backend-node/src/routes/cascadingMutualExclusionRoutes.ts +++ b/backend-node/src/routes/cascadingMutualExclusionRoutes.ts @@ -51,3 +51,4 @@ router.get("/options/:exclusionCode", getExcludedOptions); export default router; + diff --git a/backend-node/src/services/menuCopyService.ts b/backend-node/src/services/menuCopyService.ts index a0e707c1..b12d7a4a 100644 --- a/backend-node/src/services/menuCopyService.ts +++ b/backend-node/src/services/menuCopyService.ts @@ -332,6 +332,8 @@ export class MenuCopyService { /** * 플로우 수집 + * - 화면 레이아웃에서 참조된 모든 flowId 수집 + * - dataflowConfig.flowConfig.flowId 및 selectedDiagramId 모두 수집 */ private async collectFlows( screenIds: Set, @@ -340,6 +342,7 @@ export class MenuCopyService { logger.info(`🔄 플로우 수집 시작: ${screenIds.size}개 화면`); const flowIds = new Set(); + const flowDetails: Array<{ flowId: number; flowName: string; screenId: number }> = []; for (const screenId of screenIds) { const layoutsResult = await client.query( @@ -352,13 +355,35 @@ export class MenuCopyService { // webTypeConfig.dataflowConfig.flowConfig.flowId const flowId = props?.webTypeConfig?.dataflowConfig?.flowConfig?.flowId; - if (flowId) { - flowIds.add(flowId); + const flowName = props?.webTypeConfig?.dataflowConfig?.flowConfig?.flowName || "Unknown"; + + if (flowId && typeof flowId === "number" && flowId > 0) { + if (!flowIds.has(flowId)) { + flowIds.add(flowId); + flowDetails.push({ flowId, flowName, screenId }); + logger.info(` 📎 화면 ${screenId}에서 플로우 발견: id=${flowId}, name="${flowName}"`); + } + } + + // selectedDiagramId도 확인 (flowId와 동일할 수 있지만 다를 수도 있음) + const selectedDiagramId = props?.webTypeConfig?.dataflowConfig?.selectedDiagramId; + if (selectedDiagramId && typeof selectedDiagramId === "number" && selectedDiagramId > 0) { + if (!flowIds.has(selectedDiagramId)) { + flowIds.add(selectedDiagramId); + flowDetails.push({ flowId: selectedDiagramId, flowName: "SelectedDiagram", screenId }); + logger.info(` 📎 화면 ${screenId}에서 selectedDiagramId 발견: id=${selectedDiagramId}`); + } } } } - logger.info(`✅ 플로우 수집 완료: ${flowIds.size}개`); + if (flowIds.size > 0) { + logger.info(`✅ 플로우 수집 완료: ${flowIds.size}개`); + logger.info(` 📋 수집된 flowIds: [${Array.from(flowIds).join(", ")}]`); + } else { + logger.info(`📭 수집된 플로우 없음 (화면에 플로우 참조가 없음)`); + } + return flowIds; } @@ -473,15 +498,21 @@ export class MenuCopyService { } } - // flowId 매핑 (숫자 또는 숫자 문자열) - if (key === "flowId") { + // flowId, selectedDiagramId 매핑 (숫자 또는 숫자 문자열) + // selectedDiagramId는 dataflowConfig에서 flowId와 동일한 값을 참조하므로 함께 변환 + if (key === "flowId" || key === "selectedDiagramId") { const numValue = typeof value === "number" ? value : parseInt(value); - if (!isNaN(numValue)) { + if (!isNaN(numValue) && numValue > 0) { const newId = flowIdMap.get(numValue); if (newId) { obj[key] = typeof value === "number" ? newId : String(newId); // 원래 타입 유지 - logger.debug( - ` 🔗 플로우 참조 업데이트 (${currentPath}): ${value} → ${newId}` + logger.info( + ` 🔗 플로우 참조 업데이트 (${currentPath}): ${value} → ${newId}` + ); + } else { + // 매핑이 없으면 경고 로그 + logger.warn( + ` ⚠️ 플로우 매핑 없음 (${currentPath}): ${value} - 원본 플로우가 복사되지 않았을 수 있음` ); } } @@ -742,6 +773,8 @@ export class MenuCopyService { /** * 플로우 복사 + * - 대상 회사에 같은 이름+테이블의 플로우가 있으면 재사용 (ID 매핑만) + * - 없으면 새로 복사 */ private async copyFlows( flowIds: Set, @@ -757,10 +790,11 @@ export class MenuCopyService { } logger.info(`🔄 플로우 복사 중: ${flowIds.size}개`); + logger.info(` 📋 복사 대상 flowIds: [${Array.from(flowIds).join(", ")}]`); for (const originalFlowId of flowIds) { try { - // 1) flow_definition 조회 + // 1) 원본 flow_definition 조회 const flowDefResult = await client.query( `SELECT * FROM flow_definition WHERE id = $1`, [originalFlowId] @@ -772,8 +806,29 @@ export class MenuCopyService { } const flowDef = flowDefResult.rows[0]; + logger.info(` 🔍 원본 플로우 발견: id=${originalFlowId}, name="${flowDef.name}", table="${flowDef.table_name}", company="${flowDef.company_code}"`); - // 2) flow_definition 복사 + // 2) 대상 회사에 이미 같은 이름+테이블의 플로우가 있는지 확인 + const existingFlowResult = await client.query<{ id: number }>( + `SELECT id FROM flow_definition + WHERE company_code = $1 AND name = $2 AND table_name = $3 + LIMIT 1`, + [targetCompanyCode, flowDef.name, flowDef.table_name] + ); + + let newFlowId: number; + + if (existingFlowResult.rows.length > 0) { + // 기존 플로우가 있으면 재사용 + newFlowId = existingFlowResult.rows[0].id; + flowIdMap.set(originalFlowId, newFlowId); + logger.info( + ` ♻️ 기존 플로우 재사용: ${originalFlowId} → ${newFlowId} (${flowDef.name})` + ); + continue; // 스텝/연결 복사 생략 (기존 것 사용) + } + + // 3) 새 flow_definition 복사 const newFlowResult = await client.query<{ id: number }>( `INSERT INTO flow_definition ( name, description, table_name, is_active, @@ -792,11 +847,11 @@ export class MenuCopyService { ] ); - const newFlowId = newFlowResult.rows[0].id; + newFlowId = newFlowResult.rows[0].id; flowIdMap.set(originalFlowId, newFlowId); logger.info( - ` ✅ 플로우 복사: ${originalFlowId} → ${newFlowId} (${flowDef.name})` + ` ✅ 플로우 신규 복사: ${originalFlowId} → ${newFlowId} (${flowDef.name})` ); // 3) flow_step 복사 diff --git a/docs/노드플로우_개선사항.md b/docs/노드플로우_개선사항.md index 985d730a..a181ac21 100644 --- a/docs/노드플로우_개선사항.md +++ b/docs/노드플로우_개선사항.md @@ -583,3 +583,4 @@ const result = await executeNodeFlow(flowId, { + diff --git a/docs/메일발송_기능_사용_가이드.md b/docs/메일발송_기능_사용_가이드.md index 285dc6ba..916fbc54 100644 --- a/docs/메일발송_기능_사용_가이드.md +++ b/docs/메일발송_기능_사용_가이드.md @@ -356,3 +356,4 @@ - [ ] 발송 버튼의 데이터 소스가 올바르게 설정되어 있는가? + diff --git a/frontend/hooks/useAutoFill.ts b/frontend/hooks/useAutoFill.ts index 835a4886..76243569 100644 --- a/frontend/hooks/useAutoFill.ts +++ b/frontend/hooks/useAutoFill.ts @@ -193,3 +193,4 @@ export function applyAutoFillToFormData( } + diff --git a/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md b/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md index 48bae8dd..baebafe2 100644 --- a/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md +++ b/화면_임베딩_및_데이터_전달_시스템_구현_계획서.md @@ -1685,3 +1685,4 @@ const 출고등록_설정: ScreenSplitPanel = { + diff --git a/화면_임베딩_시스템_Phase1-4_구현_완료.md b/화면_임베딩_시스템_Phase1-4_구현_완료.md index 179cdd9d..3d4ac8db 100644 --- a/화면_임베딩_시스템_Phase1-4_구현_완료.md +++ b/화면_임베딩_시스템_Phase1-4_구현_완료.md @@ -532,3 +532,4 @@ const { data: config } = await getScreenSplitPanel(screenId); + diff --git a/화면_임베딩_시스템_충돌_분석_보고서.md b/화면_임베딩_시스템_충돌_분석_보고서.md index c5a9a585..5dad3e7d 100644 --- a/화면_임베딩_시스템_충돌_분석_보고서.md +++ b/화면_임베딩_시스템_충돌_분석_보고서.md @@ -519,3 +519,4 @@ function ScreenViewPage() { +