diff --git a/backend-node/src/controllers/dataflowExecutionController.ts b/backend-node/src/controllers/dataflowExecutionController.ts index 68ae2507..766ba90c 100644 --- a/backend-node/src/controllers/dataflowExecutionController.ts +++ b/backend-node/src/controllers/dataflowExecutionController.ts @@ -6,11 +6,9 @@ import { Request, Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; -import { PrismaClient } from "@prisma/client"; +import prisma from "../config/database"; import logger from "../utils/logger"; -const prisma = new PrismaClient(); - /** * 데이터 액션 실행 */ @@ -102,16 +100,34 @@ async function executeExternalDatabaseAction( connection: any ): Promise { try { - // TODO: 외부 데이터베이스 연결 및 실행 로직 구현 - // 현재는 로그만 출력하고 성공으로 처리 logger.info(`외부 DB 액션 실행: ${connection.name} (${connection.host}:${connection.port})`); logger.info(`테이블: ${tableName}, 액션: ${actionType}`, data); - // 임시 성공 응답 + // 🔥 실제 외부 DB 연결 및 실행 로직 구현 + const { MultiConnectionQueryService } = await import('../services/multiConnectionQueryService'); + const queryService = new MultiConnectionQueryService(); + + let result; + switch (actionType.toLowerCase()) { + case 'insert': + result = await queryService.insertDataToConnection(connection.id, tableName, data); + logger.info(`외부 DB INSERT 성공:`, result); + break; + case 'update': + // TODO: UPDATE 로직 구현 (조건 필요) + throw new Error('UPDATE 액션은 아직 지원되지 않습니다. 조건 설정이 필요합니다.'); + case 'delete': + // TODO: DELETE 로직 구현 (조건 필요) + throw new Error('DELETE 액션은 아직 지원되지 않습니다. 조건 설정이 필요합니다.'); + default: + throw new Error(`지원하지 않는 액션 타입: ${actionType}`); + } + return { success: true, message: `외부 DB 액션 실행 완료: ${actionType} on ${tableName}`, connection: connection.name, + data: result, affectedRows: 1, }; } catch (error) { diff --git a/backend-node/src/database/MSSQLConnector.ts b/backend-node/src/database/MSSQLConnector.ts index fc1c195c..d382c87f 100644 --- a/backend-node/src/database/MSSQLConnector.ts +++ b/backend-node/src/database/MSSQLConnector.ts @@ -77,10 +77,17 @@ export class MSSQLConnector implements DatabaseConnector { } } - async executeQuery(query: string): Promise { + async executeQuery(query: string, params: any[] = []): Promise { try { await this.connect(); - const result = await this.pool!.request().query(query); + const request = this.pool!.request(); + + // 파라미터 바인딩 (SQL Server는 @param1, @param2 형식이지만 여기서는 ? 사용) + params.forEach((param, index) => { + request.input(`param${index + 1}`, param); + }); + + const result = await request.query(query); return { rows: result.recordset, rowCount: result.rowsAffected[0], diff --git a/backend-node/src/database/MariaDBConnector.ts b/backend-node/src/database/MariaDBConnector.ts index f023bfc7..3f469330 100644 --- a/backend-node/src/database/MariaDBConnector.ts +++ b/backend-node/src/database/MariaDBConnector.ts @@ -61,13 +61,14 @@ export class MariaDBConnector implements DatabaseConnector { } } - async executeQuery(query: string): Promise { + async executeQuery(query: string, params: any[] = []): Promise { try { await this.connect(); - const [rows, fields] = await this.connection!.query(query); + const [rows, fields] = await this.connection!.query(query, params); await this.disconnect(); return { rows: rows as any[], + rowCount: Array.isArray(rows) ? rows.length : 0, fields: fields as any[], }; } catch (error: any) { diff --git a/backend-node/src/database/MySQLConnector.ts b/backend-node/src/database/MySQLConnector.ts index 450e5472..ea9b633a 100644 --- a/backend-node/src/database/MySQLConnector.ts +++ b/backend-node/src/database/MySQLConnector.ts @@ -76,13 +76,13 @@ export class MySQLConnector implements DatabaseConnector { } } - async executeQuery(query: string): Promise { + async executeQuery(query: string, params: any[] = []): Promise { if (!this.connection) { await this.connect(); } try { - const [rows, fields] = await this.connection!.query(query); + const [rows, fields] = await this.connection!.query(query, params); return { rows: rows as any[], rowCount: Array.isArray(rows) ? rows.length : 0, diff --git a/backend-node/src/database/PostgreSQLConnector.ts b/backend-node/src/database/PostgreSQLConnector.ts index 820e3fd1..52f9ce19 100644 --- a/backend-node/src/database/PostgreSQLConnector.ts +++ b/backend-node/src/database/PostgreSQLConnector.ts @@ -117,10 +117,10 @@ export class PostgreSQLConnector implements DatabaseConnector { } } - async executeQuery(query: string): Promise { + async executeQuery(query: string, params: any[] = []): Promise { try { await this.connect(); - const result = await this.client!.query(query); + const result = await this.client!.query(query, params); await this.disconnect(); return { rows: result.rows, diff --git a/backend-node/src/services/authService.ts b/backend-node/src/services/authService.ts index 1502b97f..357a90c7 100644 --- a/backend-node/src/services/authService.ts +++ b/backend-node/src/services/authService.ts @@ -1,14 +1,12 @@ // 인증 서비스 // 기존 Java LoginService를 Node.js로 포팅 -import { PrismaClient } from "@prisma/client"; +import prisma from "../config/database"; import { JwtUtils } from "../utils/jwtUtils"; import { EncryptUtil } from "../utils/encryptUtil"; import { PersonBean, LoginResult, LoginLogData } from "../types/auth"; import { logger } from "../utils/logger"; -const prisma = new PrismaClient(); - export class AuthService { /** * 기존 Java LoginService.loginPwdCheck() 메서드 포팅 diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index fca352ff..48f24cda 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -1,8 +1,7 @@ import { PrismaClient } from "@prisma/client"; +import prisma from "../config/database"; import { logger } from "../utils/logger"; -const prisma = new PrismaClient(); - export interface CodeCategory { category_code: string; category_name: string; diff --git a/backend-node/src/services/dataService.ts b/backend-node/src/services/dataService.ts index fc5935a8..f4ef2e1b 100644 --- a/backend-node/src/services/dataService.ts +++ b/backend-node/src/services/dataService.ts @@ -1,6 +1,5 @@ import { PrismaClient } from "@prisma/client"; - -const prisma = new PrismaClient(); +import prisma from "../config/database"; interface GetTableDataParams { tableName: string; diff --git a/backend-node/src/services/dataflowDiagramService.ts b/backend-node/src/services/dataflowDiagramService.ts index 427b60c5..8531ec3d 100644 --- a/backend-node/src/services/dataflowDiagramService.ts +++ b/backend-node/src/services/dataflowDiagramService.ts @@ -1,8 +1,7 @@ -import { PrismaClient, Prisma } from "@prisma/client"; +import { Prisma } from "@prisma/client"; +import prisma from "../config/database"; import { logger } from "../utils/logger"; -const prisma = new PrismaClient(); - // 타입 정의 interface CreateDataflowDiagramData { diagram_name: string; diff --git a/backend-node/src/services/entityJoinService.ts b/backend-node/src/services/entityJoinService.ts index b88c0c8b..3b00ee5c 100644 --- a/backend-node/src/services/entityJoinService.ts +++ b/backend-node/src/services/entityJoinService.ts @@ -1,4 +1,5 @@ import { PrismaClient } from "@prisma/client"; +import prisma from "../config/database"; import { logger } from "../utils/logger"; import { EntityJoinConfig, @@ -7,8 +8,6 @@ import { } from "../types/tableManagement"; import { referenceCacheService } from "./referenceCacheService"; -const prisma = new PrismaClient(); - /** * Entity 조인 기능을 제공하는 서비스 * ID값을 의미있는 데이터로 자동 변환하는 스마트 테이블 시스템 diff --git a/backend-node/src/services/externalCallConfigService.ts b/backend-node/src/services/externalCallConfigService.ts index 2ad6d629..d1120bc6 100644 --- a/backend-node/src/services/externalCallConfigService.ts +++ b/backend-node/src/services/externalCallConfigService.ts @@ -1,8 +1,6 @@ -import { PrismaClient } from "@prisma/client"; +import prisma from "../config/database"; import { logger } from "../utils/logger"; -const prisma = new PrismaClient(); - // 외부 호출 설정 타입 정의 export interface ExternalCallConfig { id?: number; diff --git a/backend-node/src/services/externalDbConnectionService.ts b/backend-node/src/services/externalDbConnectionService.ts index 96ece2d9..0d5fa1bc 100644 --- a/backend-node/src/services/externalDbConnectionService.ts +++ b/backend-node/src/services/externalDbConnectionService.ts @@ -10,6 +10,7 @@ import { } from "../types/externalDbTypes"; import { PasswordEncryption } from "../utils/passwordEncryption"; import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory"; +import logger from "../utils/logger"; export class ExternalDbConnectionService { /** @@ -694,7 +695,8 @@ export class ExternalDbConnectionService { */ static async executeQuery( id: number, - query: string + query: string, + params: any[] = [] ): Promise> { try { // 연결 정보 조회 @@ -751,7 +753,20 @@ export class ExternalDbConnectionService { let result; try { - result = await connector.executeQuery(query); + const dbType = connection.db_type?.toLowerCase() || 'postgresql'; + + // 파라미터 바인딩을 지원하는 DB 타입들 + const supportedDbTypes = ['oracle', 'mysql', 'mariadb', 'postgresql', 'sqlite', 'sqlserver', 'mssql']; + + if (supportedDbTypes.includes(dbType) && params.length > 0) { + // 파라미터 바인딩 지원 DB: 안전한 파라미터 바인딩 사용 + logger.info(`${dbType.toUpperCase()} 파라미터 바인딩 실행:`, { query, params }); + result = await (connector as any).executeQuery(query, params); + } else { + // 파라미터가 없거나 지원하지 않는 DB: 기본 방식 사용 + logger.info(`${dbType.toUpperCase()} 기본 쿼리 실행:`, { query }); + result = await connector.executeQuery(query); + } } finally { // 🔧 연결 해제 추가 - 메모리 누수 방지 await DatabaseConnectorFactory.closeConnector(id, connection.db_type); diff --git a/backend-node/src/services/layoutService.ts b/backend-node/src/services/layoutService.ts index 0440f21a..374e4e96 100644 --- a/backend-node/src/services/layoutService.ts +++ b/backend-node/src/services/layoutService.ts @@ -1,4 +1,5 @@ import { PrismaClient } from "@prisma/client"; +import prisma from "../config/database"; import { CreateLayoutRequest, UpdateLayoutRequest, @@ -7,8 +8,6 @@ import { LayoutCategory, } from "../types/layout"; -const prisma = new PrismaClient(); - // JSON 데이터를 안전하게 파싱하는 헬퍼 함수 function safeJSONParse(data: any): any { if (data === null || data === undefined) { diff --git a/backend-node/src/services/multiConnectionQueryService.ts b/backend-node/src/services/multiConnectionQueryService.ts index 22ed667f..5ce9ca68 100644 --- a/backend-node/src/services/multiConnectionQueryService.ts +++ b/backend-node/src/services/multiConnectionQueryService.ts @@ -144,21 +144,133 @@ export class MultiConnectionQueryService { } const connection = connectionResult.data; - // INSERT 쿼리 구성 + // INSERT 쿼리 구성 (DB 타입별 처리) const columns = Object.keys(data); - const values = Object.values(data); - const placeholders = values.map((_, index) => `$${index + 1}`).join(", "); + let values = Object.values(data); + + // Oracle의 경우 테이블 스키마 확인 및 데이터 타입 변환 처리 + if (connection.db_type?.toLowerCase() === 'oracle') { + try { + // Oracle 테이블 스키마 조회 + const schemaQuery = ` + SELECT COLUMN_NAME, DATA_TYPE, NULLABLE, DATA_DEFAULT + FROM USER_TAB_COLUMNS + WHERE TABLE_NAME = UPPER('${tableName}') + ORDER BY COLUMN_ID + `; + + logger.info(`🔍 Oracle 테이블 스키마 조회: ${schemaQuery}`); + + const schemaResult = await ExternalDbConnectionService.executeQuery( + connectionId, + schemaQuery + ); + + if (schemaResult.success && schemaResult.data) { + logger.info(`📋 Oracle 테이블 ${tableName} 스키마:`); + schemaResult.data.forEach((col: any) => { + logger.info(` - ${col.COLUMN_NAME}: ${col.DATA_TYPE}, NULL: ${col.NULLABLE}, DEFAULT: ${col.DATA_DEFAULT || 'None'}`); + }); + + // 필수 컬럼 중 누락된 컬럼이 있는지 확인 (기본값이 없는 NOT NULL 컬럼만) + const providedColumns = columns.map(col => col.toUpperCase()); + const missingRequiredColumns = schemaResult.data.filter((schemaCol: any) => + schemaCol.NULLABLE === 'N' && + !schemaCol.DATA_DEFAULT && + !providedColumns.includes(schemaCol.COLUMN_NAME) + ); + + if (missingRequiredColumns.length > 0) { + const missingNames = missingRequiredColumns.map((col: any) => col.COLUMN_NAME); + logger.error(`❌ 필수 컬럼 누락: ${missingNames.join(', ')}`); + throw new Error(`필수 컬럼이 누락되었습니다: ${missingNames.join(', ')}`); + } + + logger.info(`✅ 스키마 검증 통과: 모든 필수 컬럼이 제공되었거나 기본값이 있습니다.`); + } + } catch (schemaError) { + logger.warn(`⚠️ 스키마 조회 실패 (계속 진행): ${schemaError}`); + } + + values = values.map(value => { + // null이나 undefined는 그대로 유지 + if (value === null || value === undefined) { + return value; + } + + // 숫자로 변환 가능한 문자열은 숫자로 변환 + if (typeof value === 'string' && value.trim() !== '') { + const numValue = Number(value); + if (!isNaN(numValue)) { + logger.info(`🔄 Oracle 데이터 타입 변환: "${value}" (string) → ${numValue} (number)`); + return numValue; + } + } + + return value; + }); + } + + let query: string; + let queryParams: any[]; + const dbType = connection.db_type?.toLowerCase() || 'postgresql'; - const query = ` - INSERT INTO ${tableName} (${columns.join(", ")}) - VALUES (${placeholders}) - RETURNING * - `; + switch (dbType) { + case 'oracle': + // Oracle: :1, :2 스타일 바인딩 사용, RETURNING 미지원 + const oraclePlaceholders = values.map((_, index) => `:${index + 1}`).join(", "); + query = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${oraclePlaceholders})`; + queryParams = values; + logger.info(`🔍 Oracle INSERT 상세 정보:`); + logger.info(` - 테이블: ${tableName}`); + logger.info(` - 컬럼: ${JSON.stringify(columns)}`); + logger.info(` - 값: ${JSON.stringify(values)}`); + logger.info(` - 쿼리: ${query}`); + logger.info(` - 파라미터: ${JSON.stringify(queryParams)}`); + logger.info(` - 데이터 타입: ${JSON.stringify(values.map(v => typeof v))}`); + break; + + case 'mysql': + case 'mariadb': + // MySQL/MariaDB: ? 스타일 바인딩 사용, RETURNING 미지원 + const mysqlPlaceholders = values.map(() => '?').join(", "); + query = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${mysqlPlaceholders})`; + queryParams = values; + logger.info(`MySQL/MariaDB INSERT 쿼리:`, { query, params: queryParams }); + break; + + case 'sqlserver': + case 'mssql': + // SQL Server: @param1, @param2 스타일 바인딩 사용 + const sqlServerPlaceholders = values.map((_, index) => `@param${index + 1}`).join(", "); + query = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${sqlServerPlaceholders})`; + queryParams = values; + logger.info(`SQL Server INSERT 쿼리:`, { query, params: queryParams }); + break; + + case 'sqlite': + // SQLite: ? 스타일 바인딩 사용, RETURNING 지원 (3.35.0+) + const sqlitePlaceholders = values.map(() => '?').join(", "); + query = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${sqlitePlaceholders}) RETURNING *`; + queryParams = values; + logger.info(`SQLite INSERT 쿼리:`, { query, params: queryParams }); + break; + + case 'postgresql': + default: + // PostgreSQL: $1, $2 스타일 바인딩 사용, RETURNING 지원 + const pgPlaceholders = values.map((_, index) => `$${index + 1}`).join(", "); + query = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${pgPlaceholders}) RETURNING *`; + queryParams = values; + logger.info(`PostgreSQL INSERT 쿼리:`, { query, params: queryParams }); + break; + } // 외부 DB에서 쿼리 실행 const result = await ExternalDbConnectionService.executeQuery( connectionId, - query + query, + queryParams ); if (!result.success || !result.data) { diff --git a/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx b/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx index 7a72d0d0..db25934f 100644 --- a/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx +++ b/frontend/components/dataflow/connection/redesigned/DataConnectionDesigner.tsx @@ -575,40 +575,86 @@ const DataConnectionDesigner: React.FC = ({ setState((prev) => ({ ...prev, isLoading: true })); try { - // 실제 저장 로직 구현 - const saveData = { + // 실제 저장 로직 구현 - connectionType에 따라 필요한 설정만 포함 + let saveData: any = { relationshipName: state.relationshipName, description: state.description, connectionType: state.connectionType, - // 외부호출인 경우 테이블 정보는 선택사항 - fromConnection: state.connectionType === "external_call" ? null : state.fromConnection, - toConnection: state.connectionType === "external_call" ? null : state.toConnection, - fromTable: state.connectionType === "external_call" ? null : state.fromTable, - toTable: state.connectionType === "external_call" ? null : state.toTable, - // 🔧 멀티 액션 그룹 데이터 포함 - actionGroups: state.connectionType === "external_call" ? [] : state.actionGroups, - groupsLogicalOperator: state.groupsLogicalOperator, - // 외부호출 설정 포함 - externalCallConfig: state.externalCallConfig, - // 기존 호환성을 위한 필드들 (첫 번째 액션 그룹의 첫 번째 액션에서 추출) - actionType: - state.connectionType === "external_call" - ? "external_call" - : state.actionGroups[0]?.actions[0]?.actionType || state.actionType || "insert", - controlConditions: state.connectionType === "external_call" ? [] : state.controlConditions, - actionConditions: - state.connectionType === "external_call" - ? [] - : state.actionGroups[0]?.actions[0]?.conditions || state.actionConditions || [], - fieldMappings: - state.connectionType === "external_call" - ? [] - : state.actionGroups[0]?.actions[0]?.fieldMappings || state.fieldMappings || [], }; + if (state.connectionType === "external_call") { + // 외부호출 타입인 경우: 외부호출 설정만 포함 + console.log("💾 외부호출 타입 저장 - 외부호출 설정만 포함"); + saveData = { + ...saveData, + // 외부호출 관련 설정만 포함 + externalCallConfig: state.externalCallConfig, + actionType: "external_call", + // 데이터 저장 관련 설정은 제외 (null/빈 배열로 설정) + fromConnection: null, + toConnection: null, + fromTable: null, + toTable: null, + actionGroups: [], + controlConditions: [], + actionConditions: [], + fieldMappings: [], + }; + } else if (state.connectionType === "data_save") { + // 데이터 저장 타입인 경우: 데이터 저장 설정만 포함 + console.log("💾 데이터 저장 타입 저장 - 데이터 저장 설정만 포함"); + saveData = { + ...saveData, + // 데이터 저장 관련 설정만 포함 + fromConnection: state.fromConnection, + toConnection: state.toConnection, + fromTable: state.fromTable, + toTable: state.toTable, + actionGroups: state.actionGroups, + groupsLogicalOperator: state.groupsLogicalOperator, + controlConditions: state.controlConditions, + // 기존 호환성을 위한 필드들 (첫 번째 액션 그룹의 첫 번째 액션에서 추출) + actionType: state.actionGroups[0]?.actions[0]?.actionType || state.actionType || "insert", + actionConditions: state.actionGroups[0]?.actions[0]?.conditions || state.actionConditions || [], + fieldMappings: state.actionGroups[0]?.actions[0]?.fieldMappings || state.fieldMappings || [], + // 외부호출 관련 설정은 제외 (null로 설정) + externalCallConfig: null, + }; + } + console.log("💾 직접 저장 시작:", { saveData, diagramId, isEdit: !!diagramId }); - // 외부호출인 경우 external-call-configs에 설정 저장 + // 데이터 저장 타입인 경우 기존 외부호출 설정 정리 + if (state.connectionType === "data_save" && diagramId) { + console.log("🧹 데이터 저장 타입으로 변경 - 기존 외부호출 설정 정리"); + try { + const { ExternalCallConfigAPI } = await import("@/lib/api/externalCallConfig"); + + // 기존 외부호출 설정이 있는지 확인하고 삭제 또는 비활성화 + const existingConfigs = await ExternalCallConfigAPI.getConfigs({ + company_code: "*", + is_active: "Y", + }); + + const existingConfig = existingConfigs.data?.find( + (config: any) => config.config_name === (state.relationshipName || "외부호출 설정") + ); + + if (existingConfig) { + console.log("🗑️ 기존 외부호출 설정 비활성화:", existingConfig.id); + // 설정을 비활성화 (삭제하지 않고 is_active를 'N'으로 변경) + await ExternalCallConfigAPI.updateConfig(existingConfig.id, { + ...existingConfig, + is_active: "N", + updated_at: new Date().toISOString(), + }); + } + } catch (cleanupError) { + console.warn("⚠️ 외부호출 설정 정리 실패 (무시하고 계속):", cleanupError); + } + } + + // 외부호출인 경우에만 external-call-configs에 설정 저장 if (state.connectionType === "external_call" && state.externalCallConfig) { try { const { ExternalCallConfigAPI } = await import("@/lib/api/externalCallConfig"); diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 081cf93a..264bd039 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -784,7 +784,21 @@ export class ButtonActionExecutor { // 🔥 새로운 버튼 액션 실행 시스템 사용 if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) { console.log("🔗 관계 기반 제어 실행:", config.dataflowConfig.relationshipConfig); + + // 🔥 table-selection 모드일 때 선택된 행 데이터를 formData에 병합 + let mergedFormData = { ...context.formData } || {}; + if (controlDataSource === "table-selection" && context.selectedRowsData && context.selectedRowsData.length > 0) { + // 선택된 첫 번째 행의 데이터를 formData에 병합 + const selectedRowData = context.selectedRowsData[0]; + mergedFormData = { ...mergedFormData, ...selectedRowData }; + console.log("🔄 선택된 행 데이터를 formData에 병합:", { + originalFormData: context.formData, + selectedRowData, + mergedFormData, + }); + } + // 새로운 ImprovedButtonActionExecutor 사용 const buttonConfig = { actionType: config.type, @@ -794,10 +808,10 @@ export class ButtonActionExecutor { const executionResult = await ImprovedButtonActionExecutor.executeButtonAction( buttonConfig, - context.formData || {}, + mergedFormData, { buttonId: context.buttonId || "unknown", - screenId: context.screenId || "unknown", + screenId: context.screenId || "unknown", userId: context.userId || "unknown", companyCode: context.companyCode || "*", startTime: Date.now(), diff --git a/frontend/lib/utils/improvedButtonActionExecutor.ts b/frontend/lib/utils/improvedButtonActionExecutor.ts index 6c02172e..fed8a636 100644 --- a/frontend/lib/utils/improvedButtonActionExecutor.ts +++ b/frontend/lib/utils/improvedButtonActionExecutor.ts @@ -645,11 +645,27 @@ export class ImprovedButtonActionExecutor { formData: Record, context: ButtonExecutionContext ): boolean { + console.log(`🔍 조건 평가 시작:`, { + conditions, + formDataKeys: Object.keys(formData), + formData, + contextData: context.contextData, + }); + for (const condition of conditions) { const fieldValue = formData[condition.field]; const conditionValue = condition.value; const operator = condition.operator; + console.log(`🔍 개별 조건 검증:`, { + field: condition.field, + operator, + expectedValue: conditionValue, + actualValue: fieldValue, + formDataHasField: condition.field in formData, + allFormDataKeys: Object.keys(formData), + }); + let conditionMet = false; switch (operator) { case '=': @@ -677,6 +693,8 @@ export class ImprovedButtonActionExecutor { if (!conditionMet) { console.log(`❌ 조건 불만족: ${condition.field} ${operator} ${conditionValue} (실제값: ${fieldValue})`); + console.log(`❌ 사용 가능한 필드들:`, Object.keys(formData)); + console.log(`❌ 전체 formData:`, formData); return false; } }