From ee3a648917ca06420d4d2692d7ca26dec764275b Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 9 Jan 2026 13:43:14 +0900 Subject: [PATCH] =?UTF-8?q?=EC=82=AD=EC=A0=9C=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=20=EB=8F=99=EC=9E=91=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8D=98=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/dynamicFormController.ts | 13 +- .../src/services/dynamicFormService.ts | 39 ++- .../src/services/nodeFlowExecutionService.ts | 112 +++++-- .../properties/InsertActionProperties.tsx | 314 ++++++++++++++---- frontend/components/screen/EditModal.tsx | 2 + .../screen/InteractiveScreenViewer.tsx | 3 +- frontend/lib/api/dynamicForm.ts | 11 +- frontend/lib/utils/buttonActions.ts | 17 +- frontend/types/node-editor.ts | 5 + 9 files changed, 400 insertions(+), 116 deletions(-) diff --git a/backend-node/src/controllers/dynamicFormController.ts b/backend-node/src/controllers/dynamicFormController.ts index 98606f51..48b55d18 100644 --- a/backend-node/src/controllers/dynamicFormController.ts +++ b/backend-node/src/controllers/dynamicFormController.ts @@ -231,7 +231,7 @@ export const deleteFormData = async ( try { const { id } = req.params; const { companyCode, userId } = req.user as any; - const { tableName } = req.body; + const { tableName, screenId } = req.body; if (!tableName) { return res.status(400).json({ @@ -240,7 +240,16 @@ export const deleteFormData = async ( }); } - await dynamicFormService.deleteFormData(id, tableName, companyCode, userId); // userId 추가 + // screenId를 숫자로 변환 (문자열로 전달될 수 있음) + const parsedScreenId = screenId ? parseInt(screenId, 10) : undefined; + + await dynamicFormService.deleteFormData( + id, + tableName, + companyCode, + userId, + parsedScreenId // screenId 추가 (제어관리 실행용) + ); res.json({ success: true, diff --git a/backend-node/src/services/dynamicFormService.ts b/backend-node/src/services/dynamicFormService.ts index 8337ed74..89d96859 100644 --- a/backend-node/src/services/dynamicFormService.ts +++ b/backend-node/src/services/dynamicFormService.ts @@ -1192,12 +1192,18 @@ export class DynamicFormService { /** * 폼 데이터 삭제 (실제 테이블에서 직접 삭제) + * @param id 삭제할 레코드 ID + * @param tableName 테이블명 + * @param companyCode 회사 코드 + * @param userId 사용자 ID + * @param screenId 화면 ID (제어관리 실행용, 선택사항) */ async deleteFormData( id: string | number, tableName: string, companyCode?: string, - userId?: string + userId?: string, + screenId?: number ): Promise { try { console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", { @@ -1310,14 +1316,19 @@ export class DynamicFormService { const recordCompanyCode = deletedRecord?.company_code || companyCode || "*"; - await this.executeDataflowControlIfConfigured( - 0, // DELETE는 screenId를 알 수 없으므로 0으로 설정 (추후 개선 필요) - tableName, - deletedRecord, - "delete", - userId || "system", - recordCompanyCode - ); + // screenId가 전달되지 않으면 제어관리를 실행하지 않음 + if (screenId && screenId > 0) { + await this.executeDataflowControlIfConfigured( + screenId, + tableName, + deletedRecord, + "delete", + userId || "system", + recordCompanyCode + ); + } else { + console.log("ℹ️ screenId가 전달되지 않아 제어관리를 건너뜁니다. (screenId:", screenId, ")"); + } } } catch (controlError) { console.error("⚠️ 제어관리 실행 오류:", controlError); @@ -1662,10 +1673,16 @@ export class DynamicFormService { !!properties?.webTypeConfig?.dataflowConfig?.flowControls, }); - // 버튼 컴포넌트이고 저장 액션이며 제어관리가 활성화된 경우 + // 버튼 컴포넌트이고 제어관리가 활성화된 경우 + // triggerType에 맞는 액션 타입 매칭: insert/update -> save, delete -> delete + const buttonActionType = properties?.componentConfig?.action?.type; + const isMatchingAction = + (triggerType === "delete" && buttonActionType === "delete") || + ((triggerType === "insert" || triggerType === "update") && buttonActionType === "save"); + if ( properties?.componentType === "button-primary" && - properties?.componentConfig?.action?.type === "save" && + isMatchingAction && properties?.webTypeConfig?.enableDataflowControl === true ) { const dataflowConfig = properties?.webTypeConfig?.dataflowConfig; diff --git a/backend-node/src/services/nodeFlowExecutionService.ts b/backend-node/src/services/nodeFlowExecutionService.ts index baa1f02c..bfd628ce 100644 --- a/backend-node/src/services/nodeFlowExecutionService.ts +++ b/backend-node/src/services/nodeFlowExecutionService.ts @@ -969,21 +969,56 @@ export class NodeFlowExecutionService { const insertedData = { ...data }; console.log("🗺️ 필드 매핑 처리 중..."); - fieldMappings.forEach((mapping: any) => { + + // 🔥 채번 규칙 서비스 동적 import + const { numberingRuleService } = await import("./numberingRuleService"); + + for (const mapping of fieldMappings) { fields.push(mapping.targetField); - const value = - mapping.staticValue !== undefined - ? mapping.staticValue - : data[mapping.sourceField]; - - console.log( - ` ${mapping.sourceField} → ${mapping.targetField}: ${value === undefined ? "❌ undefined" : "✅ " + value}` - ); + let value: any; + + // 🔥 값 생성 유형에 따른 처리 + const valueType = mapping.valueType || (mapping.staticValue !== undefined ? "static" : "source"); + + if (valueType === "autoGenerate" && mapping.numberingRuleId) { + // 자동 생성 (채번 규칙) + const companyCode = context.buttonContext?.companyCode || "*"; + try { + value = await numberingRuleService.allocateCode( + mapping.numberingRuleId, + companyCode + ); + console.log( + ` 🔢 자동 생성(채번): ${mapping.targetField} = ${value} (규칙: ${mapping.numberingRuleId})` + ); + } catch (error: any) { + logger.error(`채번 규칙 적용 실패: ${error.message}`); + console.error( + ` ❌ 채번 실패 → ${mapping.targetField}: ${error.message}` + ); + throw new Error( + `채번 규칙 '${mapping.numberingRuleName || mapping.numberingRuleId}' 적용 실패: ${error.message}` + ); + } + } else if (valueType === "static" || mapping.staticValue !== undefined) { + // 고정값 + value = mapping.staticValue; + console.log( + ` 📌 고정값: ${mapping.targetField} = ${value}` + ); + } else { + // 소스 필드 + value = data[mapping.sourceField]; + console.log( + ` ${mapping.sourceField} → ${mapping.targetField}: ${value === undefined ? "❌ undefined" : "✅ " + value}` + ); + } + values.push(value); // 🔥 삽입된 값을 데이터에 반영 insertedData[mapping.targetField] = value; - }); + } // 🆕 writer와 company_code 자동 추가 (필드 매핑에 없는 경우) const hasWriterMapping = fieldMappings.some( @@ -1528,16 +1563,24 @@ export class NodeFlowExecutionService { } }); - // 🔑 Primary Key 자동 추가 (context-data 모드) - console.log("🔑 context-data 모드: Primary Key 자동 추가"); - const enhancedWhereConditions = await this.enhanceWhereConditionsWithPK( - whereConditions, - data, - targetTable - ); + // 🔑 Primary Key 자동 추가 여부 결정: + // whereConditions가 명시적으로 설정되어 있으면 PK 자동 추가를 하지 않음 + // (사용자가 직접 조건을 설정한 경우 의도를 존중) + let finalWhereConditions: any[]; + if (whereConditions && whereConditions.length > 0) { + console.log("📋 사용자 정의 WHERE 조건 사용 (PK 자동 추가 안 함)"); + finalWhereConditions = whereConditions; + } else { + console.log("🔑 context-data 모드: Primary Key 자동 추가"); + finalWhereConditions = await this.enhanceWhereConditionsWithPK( + whereConditions, + data, + targetTable + ); + } const whereResult = this.buildWhereClause( - enhancedWhereConditions, + finalWhereConditions, data, paramIndex ); @@ -1907,22 +1950,30 @@ export class NodeFlowExecutionService { return deletedDataArray; } - // 🆕 context-data 모드: 개별 삭제 (PK 자동 추가) + // 🆕 context-data 모드: 개별 삭제 console.log("🎯 context-data 모드: 개별 삭제 시작"); for (const data of dataArray) { console.log("🔍 WHERE 조건 처리 중..."); - // 🔑 Primary Key 자동 추가 (context-data 모드) - console.log("🔑 context-data 모드: Primary Key 자동 추가"); - const enhancedWhereConditions = await this.enhanceWhereConditionsWithPK( - whereConditions, - data, - targetTable - ); + // 🔑 Primary Key 자동 추가 여부 결정: + // whereConditions가 명시적으로 설정되어 있으면 PK 자동 추가를 하지 않음 + // (사용자가 직접 조건을 설정한 경우 의도를 존중) + let finalWhereConditions: any[]; + if (whereConditions && whereConditions.length > 0) { + console.log("📋 사용자 정의 WHERE 조건 사용 (PK 자동 추가 안 함)"); + finalWhereConditions = whereConditions; + } else { + console.log("🔑 context-data 모드: Primary Key 자동 추가"); + finalWhereConditions = await this.enhanceWhereConditionsWithPK( + whereConditions, + data, + targetTable + ); + } const whereResult = this.buildWhereClause( - enhancedWhereConditions, + finalWhereConditions, data, 1 ); @@ -2865,10 +2916,11 @@ export class NodeFlowExecutionService { if (fieldValue === null || fieldValue === undefined || fieldValue === "") { logger.info( - `⚠️ EXISTS 조건: 필드값이 비어있어 ${operator === "NOT_EXISTS_IN" ? "TRUE" : "FALSE"} 반환` + `⚠️ EXISTS 조건: 필드값이 비어있어 FALSE 반환 (빈 값은 조건 검사하지 않음)` ); - // 값이 비어있으면: EXISTS_IN은 false, NOT_EXISTS_IN은 true - return operator === "NOT_EXISTS_IN"; + // 값이 비어있으면 조건 검사 자체가 무의미하므로 항상 false 반환 + // 이렇게 하면 빈 값으로 인한 의도치 않은 INSERT/UPDATE/DELETE가 방지됨 + return false; } try { diff --git a/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx b/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx index 437487e9..c68ff8d4 100644 --- a/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx +++ b/frontend/components/dataflow/node-editor/panels/properties/InsertActionProperties.tsx @@ -5,7 +5,7 @@ */ import { useEffect, useState } from "react"; -import { Plus, Trash2, Check, ChevronsUpDown, ArrowRight, Database, Globe, Link2 } from "lucide-react"; +import { Plus, Trash2, Check, ChevronsUpDown, ArrowRight, Database, Globe, Link2, Sparkles } from "lucide-react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -18,6 +18,8 @@ import { cn } from "@/lib/utils"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import { tableTypeApi } from "@/lib/api/screen"; import { getTestedExternalConnections, getExternalTables, getExternalColumns } from "@/lib/api/nodeExternalConnections"; +import { getNumberingRules } from "@/lib/api/numberingRule"; +import type { NumberingRuleConfig } from "@/types/numbering-rule"; import type { InsertActionNodeData } from "@/types/node-editor"; import type { ExternalConnection, ExternalTable, ExternalColumn } from "@/lib/api/nodeExternalConnections"; @@ -89,6 +91,11 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP const [apiHeaders, setApiHeaders] = useState>(data.apiHeaders || {}); const [apiBodyTemplate, setApiBodyTemplate] = useState(data.apiBodyTemplate || ""); + // 🔥 채번 규칙 관련 상태 + const [numberingRules, setNumberingRules] = useState([]); + const [numberingRulesLoading, setNumberingRulesLoading] = useState(false); + const [mappingNumberingRulesOpenState, setMappingNumberingRulesOpenState] = useState([]); + // 데이터 변경 시 로컬 상태 업데이트 useEffect(() => { setDisplayName(data.displayName || data.targetTable); @@ -128,8 +135,33 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP useEffect(() => { setMappingSourceFieldsOpenState(new Array(fieldMappings.length).fill(false)); setMappingTargetFieldsOpenState(new Array(fieldMappings.length).fill(false)); + setMappingNumberingRulesOpenState(new Array(fieldMappings.length).fill(false)); }, [fieldMappings.length]); + // 🔥 채번 규칙 로딩 (자동 생성 사용 시) + useEffect(() => { + const loadNumberingRules = async () => { + setNumberingRulesLoading(true); + try { + const response = await getNumberingRules(); + if (response.success && response.data) { + setNumberingRules(response.data); + console.log(`✅ 채번 규칙 ${response.data.length}개 로딩 완료`); + } else { + console.error("❌ 채번 규칙 로딩 실패:", response.error); + setNumberingRules([]); + } + } catch (error) { + console.error("❌ 채번 규칙 로딩 오류:", error); + setNumberingRules([]); + } finally { + setNumberingRulesLoading(false); + } + }; + + loadNumberingRules(); + }, []); + // 🔥 외부 테이블 변경 시 컬럼 로드 useEffect(() => { if (targetType === "external" && selectedExternalConnectionId && externalTargetTable) { @@ -540,6 +572,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP sourceField: null, targetField: "", staticValue: undefined, + valueType: "source" as const, // 🔥 기본값: 소스 필드 }, ]; setFieldMappings(newMappings); @@ -548,6 +581,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP // Combobox 열림 상태 배열 초기화 setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false)); setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false)); + setMappingNumberingRulesOpenState(new Array(newMappings.length).fill(false)); }; const handleRemoveMapping = (index: number) => { @@ -558,6 +592,7 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP // Combobox 열림 상태 배열도 업데이트 setMappingSourceFieldsOpenState(new Array(newMappings.length).fill(false)); setMappingTargetFieldsOpenState(new Array(newMappings.length).fill(false)); + setMappingNumberingRulesOpenState(new Array(newMappings.length).fill(false)); }; const handleMappingChange = (index: number, field: string, value: any) => { @@ -586,6 +621,24 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP targetField: value, targetFieldLabel: targetColumn?.label_ko || targetColumn?.column_label || targetColumn?.displayName || value, }; + } else if (field === "valueType") { + // 🔥 값 생성 유형 변경 시 관련 필드 초기화 + newMappings[index] = { + ...newMappings[index], + valueType: value, + // 유형 변경 시 다른 유형의 값 초기화 + ...(value !== "source" && { sourceField: null, sourceFieldLabel: undefined }), + ...(value !== "static" && { staticValue: undefined }), + ...(value !== "autoGenerate" && { numberingRuleId: undefined, numberingRuleName: undefined }), + }; + } else if (field === "numberingRuleId") { + // 🔥 채번 규칙 선택 시 이름도 함께 저장 + const selectedRule = numberingRules.find((r) => r.ruleId === value); + newMappings[index] = { + ...newMappings[index], + numberingRuleId: value, + numberingRuleName: selectedRule?.ruleName, + }; } else { newMappings[index] = { ...newMappings[index], @@ -1165,54 +1218,203 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
- {/* 소스 필드 입력/선택 */} + {/* 🔥 값 생성 유형 선택 */}
- - {hasRestAPISource ? ( - // REST API 소스인 경우: 직접 입력 + +
+ + + +
+
+ + {/* 🔥 소스 필드 입력/선택 (valueType === "source" 일 때만) */} + {(mapping.valueType === "source" || !mapping.valueType) && ( +
+ + {hasRestAPISource ? ( + // REST API 소스인 경우: 직접 입력 + handleMappingChange(index, "sourceField", e.target.value || null)} + placeholder="필드명 입력 (예: userId, userName)" + className="mt-1 h-8 text-xs" + /> + ) : ( + // 일반 소스인 경우: Combobox 선택 + { + const newState = [...mappingSourceFieldsOpenState]; + newState[index] = open; + setMappingSourceFieldsOpenState(newState); + }} + > + + + + + + + + + 필드를 찾을 수 없습니다. + + + {sourceFields.map((field) => ( + { + handleMappingChange(index, "sourceField", currentValue || null); + const newState = [...mappingSourceFieldsOpenState]; + newState[index] = false; + setMappingSourceFieldsOpenState(newState); + }} + className="text-xs sm:text-sm" + > + +
+ {field.label || field.name} + {field.label && field.label !== field.name && ( + + {field.name} + + )} +
+
+ ))} +
+
+
+
+
+ )} + {hasRestAPISource && ( +

API 응답 JSON의 필드명을 입력하세요

+ )} +
+ )} + + {/* 🔥 고정값 입력 (valueType === "static" 일 때) */} + {mapping.valueType === "static" && ( +
+ handleMappingChange(index, "sourceField", e.target.value || null)} - placeholder="필드명 입력 (예: userId, userName)" + value={mapping.staticValue || ""} + onChange={(e) => handleMappingChange(index, "staticValue", e.target.value || undefined)} + placeholder="고정값 입력" className="mt-1 h-8 text-xs" /> - ) : ( - // 일반 소스인 경우: Combobox 선택 +
+ )} + + {/* 🔥 채번 규칙 선택 (valueType === "autoGenerate" 일 때) */} + {mapping.valueType === "autoGenerate" && ( +
+ { - const newState = [...mappingSourceFieldsOpenState]; + const newState = [...mappingNumberingRulesOpenState]; newState[index] = open; - setMappingSourceFieldsOpenState(newState); + setMappingNumberingRulesOpenState(newState); }} > @@ -1222,37 +1424,36 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP align="start" > - + - 필드를 찾을 수 없습니다. + 채번 규칙을 찾을 수 없습니다. - {sourceFields.map((field) => ( + {numberingRules.map((rule) => ( { - handleMappingChange(index, "sourceField", currentValue || null); - const newState = [...mappingSourceFieldsOpenState]; + handleMappingChange(index, "numberingRuleId", currentValue); + const newState = [...mappingNumberingRulesOpenState]; newState[index] = false; - setMappingSourceFieldsOpenState(newState); + setMappingNumberingRulesOpenState(newState); }} className="text-xs sm:text-sm" >
- {field.label || field.name} - {field.label && field.label !== field.name && ( - - {field.name} - - )} + {rule.ruleName} + + {rule.ruleId} + {rule.tableName && ` - ${rule.tableName}`} +
))} @@ -1261,11 +1462,13 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
- )} - {hasRestAPISource && ( -

API 응답 JSON의 필드명을 입력하세요

- )} -
+ {numberingRules.length === 0 && !numberingRulesLoading && ( +

+ 등록된 채번 규칙이 없습니다. 시스템 관리에서 먼저 채번 규칙을 생성하세요. +

+ )} +
+ )}
@@ -1400,18 +1603,6 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP
- - {/* 정적 값 */} -
- - handleMappingChange(index, "staticValue", e.target.value || undefined)} - placeholder="소스 필드 대신 고정 값 사용" - className="mt-1 h-8 text-xs" - /> -

소스 필드가 비어있을 때만 사용됩니다

-
))} @@ -1428,9 +1619,8 @@ export function InsertActionProperties({ nodeId, data }: InsertActionPropertiesP {/* 안내 */}
- ✅ 테이블과 필드는 실제 데이터베이스에서 조회됩니다. -
- 💡 소스 필드가 없으면 정적 값이 사용됩니다. +

테이블과 필드는 실제 데이터베이스에서 조회됩니다.

+

값 생성 방식: 소스 필드(입력값 연결) / 고정값(직접 입력) / 자동생성(채번 규칙)

diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index c1b644cc..b3c94ade 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -671,9 +671,11 @@ export const EditModal: React.FC = ({ className }) => { console.log("🗑️ 품목 삭제:", deletedItem); try { + // screenId 전달하여 제어관리 실행 가능하도록 함 const response = await dynamicFormApi.deleteFormDataFromTable( deletedItem.id, screenData.screenInfo.tableName, + modalState.screenId || screenData.screenInfo?.id, ); if (response.success) { diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index f786d1d1..9a0ffa8d 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -1676,7 +1676,8 @@ export const InteractiveScreenViewer: React.FC = ( try { // console.log("🗑️ 삭제 실행:", { recordId, tableName, formData }); - const result = await dynamicFormApi.deleteFormDataFromTable(recordId, tableName); + // screenId 전달하여 제어관리 실행 가능하도록 함 + const result = await dynamicFormApi.deleteFormDataFromTable(recordId, tableName, screenInfo?.id); if (result.success) { alert("삭제되었습니다."); diff --git a/frontend/lib/api/dynamicForm.ts b/frontend/lib/api/dynamicForm.ts index f28704cf..57fb1a8d 100644 --- a/frontend/lib/api/dynamicForm.ts +++ b/frontend/lib/api/dynamicForm.ts @@ -202,14 +202,19 @@ export class DynamicFormApi { * 실제 테이블에서 폼 데이터 삭제 * @param id 레코드 ID * @param tableName 테이블명 + * @param screenId 화면 ID (제어관리 실행용, 선택사항) * @returns 삭제 결과 */ - static async deleteFormDataFromTable(id: string | number, tableName: string): Promise> { + static async deleteFormDataFromTable( + id: string | number, + tableName: string, + screenId?: number + ): Promise> { try { - console.log("🗑️ 실제 테이블에서 폼 데이터 삭제 요청:", { id, tableName }); + console.log("🗑️ 실제 테이블에서 폼 데이터 삭제 요청:", { id, tableName, screenId }); await apiClient.delete(`/dynamic-form/${id}`, { - data: { tableName }, + data: { tableName, screenId }, }); console.log("✅ 실제 테이블에서 폼 데이터 삭제 성공"); diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index c65fba89..27f76c79 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -967,11 +967,11 @@ export class ButtonActionExecutor { deletedItemIds, }); - // 삭제 API 호출 + // 삭제 API 호출 - screenId 전달하여 제어관리 실행 가능하도록 함 for (const itemId of deletedItemIds) { try { console.log(`🗑️ [handleSave] 항목 삭제 중: ${itemId} from ${targetTable}`); - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(itemId, targetTable); + const deleteResult = await DynamicFormApi.deleteFormDataFromTable(itemId, targetTable, context.screenId); if (deleteResult.success) { console.log(`✅ [handleSave] 항목 삭제 완료: ${itemId}`); } else { @@ -1967,7 +1967,8 @@ export class ButtonActionExecutor { for (const deletedItem of deletedItems) { console.log(`🗑️ [DELETE] 품목 삭제: id=${deletedItem.id}, tableName=${saveTableName}`); - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deletedItem.id, saveTableName); + // screenId 전달하여 제어관리 실행 가능하도록 함 + const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deletedItem.id, saveTableName, context.screenId); if (!deleteResult.success) { throw new Error(deleteResult.message || "품목 삭제 실패"); @@ -2434,7 +2435,8 @@ export class ButtonActionExecutor { if (deleteId) { console.log("다중 데이터 삭제:", { tableName, screenId, id: deleteId }); - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deleteId, tableName); + // screenId 전달하여 제어관리 실행 가능하도록 함 + const deleteResult = await DynamicFormApi.deleteFormDataFromTable(deleteId, tableName, screenId); if (!deleteResult.success) { throw new Error(`ID ${deleteId} 삭제 실패: ${deleteResult.message}`); } @@ -2469,8 +2471,8 @@ export class ButtonActionExecutor { if (tableName && screenId && formData.id) { console.log("단일 데이터 삭제:", { tableName, screenId, id: formData.id }); - // 실제 삭제 API 호출 - const deleteResult = await DynamicFormApi.deleteFormDataFromTable(formData.id, tableName); + // 실제 삭제 API 호출 - screenId 전달하여 제어관리 실행 가능하도록 함 + const deleteResult = await DynamicFormApi.deleteFormDataFromTable(formData.id, tableName, screenId); if (!deleteResult.success) { throw new Error(deleteResult.message || "삭제에 실패했습니다."); @@ -4251,7 +4253,8 @@ export class ButtonActionExecutor { throw new Error("삭제할 항목의 ID를 찾을 수 없습니다."); } - const result = await DynamicFormApi.deleteFormDataFromTable(deleteId, context.tableName); + // screenId 전달하여 제어관리 실행 가능하도록 함 + const result = await DynamicFormApi.deleteFormDataFromTable(deleteId, context.tableName, context.screenId); if (result.success) { console.log("✅ 삭제 성공:", result); diff --git a/frontend/types/node-editor.ts b/frontend/types/node-editor.ts index 6eb1bb1c..9c7d5c5e 100644 --- a/frontend/types/node-editor.ts +++ b/frontend/types/node-editor.ts @@ -344,6 +344,11 @@ export interface InsertActionNodeData { targetField: string; targetFieldLabel?: string; staticValue?: any; + // 🔥 값 생성 유형 추가 + valueType?: "source" | "static" | "autoGenerate"; // 소스 필드 / 고정값 / 자동 생성 + // 자동 생성 옵션 (valueType === "autoGenerate" 일 때) + numberingRuleId?: string; // 채번 규칙 ID + numberingRuleName?: string; // 채번 규칙명 (표시용) }>; options: { batchSize?: number;