Merge remote-tracking branch 'origin/main' into feature/batch-testing-updates
This commit is contained in:
commit
c2a4a4a61e
|
|
@ -6,11 +6,9 @@
|
||||||
|
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { AuthenticatedRequest } from "../types/auth";
|
import { AuthenticatedRequest } from "../types/auth";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import prisma from "../config/database";
|
||||||
import logger from "../utils/logger";
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 데이터 액션 실행
|
* 데이터 액션 실행
|
||||||
*/
|
*/
|
||||||
|
|
@ -102,16 +100,34 @@ async function executeExternalDatabaseAction(
|
||||||
connection: any
|
connection: any
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// TODO: 외부 데이터베이스 연결 및 실행 로직 구현
|
|
||||||
// 현재는 로그만 출력하고 성공으로 처리
|
|
||||||
logger.info(`외부 DB 액션 실행: ${connection.name} (${connection.host}:${connection.port})`);
|
logger.info(`외부 DB 액션 실행: ${connection.name} (${connection.host}:${connection.port})`);
|
||||||
logger.info(`테이블: ${tableName}, 액션: ${actionType}`, data);
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `외부 DB 액션 실행 완료: ${actionType} on ${tableName}`,
|
message: `외부 DB 액션 실행 완료: ${actionType} on ${tableName}`,
|
||||||
connection: connection.name,
|
connection: connection.name,
|
||||||
|
data: result,
|
||||||
affectedRows: 1,
|
affectedRows: 1,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -77,10 +77,17 @@ export class MSSQLConnector implements DatabaseConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeQuery(query: string): Promise<QueryResult> {
|
async executeQuery(query: string, params: any[] = []): Promise<QueryResult> {
|
||||||
try {
|
try {
|
||||||
await this.connect();
|
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 {
|
return {
|
||||||
rows: result.recordset,
|
rows: result.recordset,
|
||||||
rowCount: result.rowsAffected[0],
|
rowCount: result.rowsAffected[0],
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,14 @@ export class MariaDBConnector implements DatabaseConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeQuery(query: string): Promise<QueryResult> {
|
async executeQuery(query: string, params: any[] = []): Promise<QueryResult> {
|
||||||
try {
|
try {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
const [rows, fields] = await this.connection!.query(query);
|
const [rows, fields] = await this.connection!.query(query, params);
|
||||||
await this.disconnect();
|
await this.disconnect();
|
||||||
return {
|
return {
|
||||||
rows: rows as any[],
|
rows: rows as any[],
|
||||||
|
rowCount: Array.isArray(rows) ? rows.length : 0,
|
||||||
fields: fields as any[],
|
fields: fields as any[],
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
|
||||||
|
|
@ -76,13 +76,13 @@ export class MySQLConnector implements DatabaseConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeQuery(query: string): Promise<QueryResult> {
|
async executeQuery(query: string, params: any[] = []): Promise<QueryResult> {
|
||||||
if (!this.connection) {
|
if (!this.connection) {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows, fields] = await this.connection!.query(query);
|
const [rows, fields] = await this.connection!.query(query, params);
|
||||||
return {
|
return {
|
||||||
rows: rows as any[],
|
rows: rows as any[],
|
||||||
rowCount: Array.isArray(rows) ? rows.length : 0,
|
rowCount: Array.isArray(rows) ? rows.length : 0,
|
||||||
|
|
|
||||||
|
|
@ -117,10 +117,10 @@ export class PostgreSQLConnector implements DatabaseConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeQuery(query: string): Promise<QueryResult> {
|
async executeQuery(query: string, params: any[] = []): Promise<QueryResult> {
|
||||||
try {
|
try {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
const result = await this.client!.query(query);
|
const result = await this.client!.query(query, params);
|
||||||
await this.disconnect();
|
await this.disconnect();
|
||||||
return {
|
return {
|
||||||
rows: result.rows,
|
rows: result.rows,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
// 인증 서비스
|
// 인증 서비스
|
||||||
// 기존 Java LoginService를 Node.js로 포팅
|
// 기존 Java LoginService를 Node.js로 포팅
|
||||||
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
import prisma from "../config/database";
|
||||||
import { JwtUtils } from "../utils/jwtUtils";
|
import { JwtUtils } from "../utils/jwtUtils";
|
||||||
import { EncryptUtil } from "../utils/encryptUtil";
|
import { EncryptUtil } from "../utils/encryptUtil";
|
||||||
import { PersonBean, LoginResult, LoginLogData } from "../types/auth";
|
import { PersonBean, LoginResult, LoginLogData } from "../types/auth";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
/**
|
/**
|
||||||
* 기존 Java LoginService.loginPwdCheck() 메서드 포팅
|
* 기존 Java LoginService.loginPwdCheck() 메서드 포팅
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import prisma from "../config/database";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export interface CodeCategory {
|
export interface CodeCategory {
|
||||||
category_code: string;
|
category_code: string;
|
||||||
category_name: string;
|
category_name: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import prisma from "../config/database";
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
interface GetTableDataParams {
|
interface GetTableDataParams {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
// 타입 정의
|
// 타입 정의
|
||||||
interface CreateDataflowDiagramData {
|
interface CreateDataflowDiagramData {
|
||||||
diagram_name: string;
|
diagram_name: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import prisma from "../config/database";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import {
|
import {
|
||||||
EntityJoinConfig,
|
EntityJoinConfig,
|
||||||
|
|
@ -7,8 +8,6 @@ import {
|
||||||
} from "../types/tableManagement";
|
} from "../types/tableManagement";
|
||||||
import { referenceCacheService } from "./referenceCacheService";
|
import { referenceCacheService } from "./referenceCacheService";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity 조인 기능을 제공하는 서비스
|
* Entity 조인 기능을 제공하는 서비스
|
||||||
* ID값을 의미있는 데이터로 자동 변환하는 스마트 테이블 시스템
|
* ID값을 의미있는 데이터로 자동 변환하는 스마트 테이블 시스템
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import prisma from "../config/database";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
// 외부 호출 설정 타입 정의
|
// 외부 호출 설정 타입 정의
|
||||||
export interface ExternalCallConfig {
|
export interface ExternalCallConfig {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from "../types/externalDbTypes";
|
} from "../types/externalDbTypes";
|
||||||
import { PasswordEncryption } from "../utils/passwordEncryption";
|
import { PasswordEncryption } from "../utils/passwordEncryption";
|
||||||
import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory";
|
import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory";
|
||||||
|
import logger from "../utils/logger";
|
||||||
|
|
||||||
export class ExternalDbConnectionService {
|
export class ExternalDbConnectionService {
|
||||||
/**
|
/**
|
||||||
|
|
@ -694,7 +695,8 @@ export class ExternalDbConnectionService {
|
||||||
*/
|
*/
|
||||||
static async executeQuery(
|
static async executeQuery(
|
||||||
id: number,
|
id: number,
|
||||||
query: string
|
query: string,
|
||||||
|
params: any[] = []
|
||||||
): Promise<ApiResponse<any[]>> {
|
): Promise<ApiResponse<any[]>> {
|
||||||
try {
|
try {
|
||||||
// 연결 정보 조회
|
// 연결 정보 조회
|
||||||
|
|
@ -751,7 +753,20 @@ export class ExternalDbConnectionService {
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
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 {
|
} finally {
|
||||||
// 🔧 연결 해제 추가 - 메모리 누수 방지
|
// 🔧 연결 해제 추가 - 메모리 누수 방지
|
||||||
await DatabaseConnectorFactory.closeConnector(id, connection.db_type);
|
await DatabaseConnectorFactory.closeConnector(id, connection.db_type);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import prisma from "../config/database";
|
||||||
import {
|
import {
|
||||||
CreateLayoutRequest,
|
CreateLayoutRequest,
|
||||||
UpdateLayoutRequest,
|
UpdateLayoutRequest,
|
||||||
|
|
@ -7,8 +8,6 @@ import {
|
||||||
LayoutCategory,
|
LayoutCategory,
|
||||||
} from "../types/layout";
|
} from "../types/layout";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
// JSON 데이터를 안전하게 파싱하는 헬퍼 함수
|
// JSON 데이터를 안전하게 파싱하는 헬퍼 함수
|
||||||
function safeJSONParse(data: any): any {
|
function safeJSONParse(data: any): any {
|
||||||
if (data === null || data === undefined) {
|
if (data === null || data === undefined) {
|
||||||
|
|
|
||||||
|
|
@ -144,21 +144,133 @@ export class MultiConnectionQueryService {
|
||||||
}
|
}
|
||||||
const connection = connectionResult.data;
|
const connection = connectionResult.data;
|
||||||
|
|
||||||
// INSERT 쿼리 구성
|
// INSERT 쿼리 구성 (DB 타입별 처리)
|
||||||
const columns = Object.keys(data);
|
const columns = Object.keys(data);
|
||||||
const values = Object.values(data);
|
let values = Object.values(data);
|
||||||
const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
|
|
||||||
|
// 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 = `
|
switch (dbType) {
|
||||||
INSERT INTO ${tableName} (${columns.join(", ")})
|
case 'oracle':
|
||||||
VALUES (${placeholders})
|
// Oracle: :1, :2 스타일 바인딩 사용, RETURNING 미지원
|
||||||
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에서 쿼리 실행
|
// 외부 DB에서 쿼리 실행
|
||||||
const result = await ExternalDbConnectionService.executeQuery(
|
const result = await ExternalDbConnectionService.executeQuery(
|
||||||
connectionId,
|
connectionId,
|
||||||
query
|
query,
|
||||||
|
queryParams
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.success || !result.data) {
|
if (!result.success || !result.data) {
|
||||||
|
|
|
||||||
|
|
@ -575,40 +575,86 @@ const DataConnectionDesigner: React.FC<DataConnectionDesignerProps> = ({
|
||||||
setState((prev) => ({ ...prev, isLoading: true }));
|
setState((prev) => ({ ...prev, isLoading: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 실제 저장 로직 구현
|
// 실제 저장 로직 구현 - connectionType에 따라 필요한 설정만 포함
|
||||||
const saveData = {
|
let saveData: any = {
|
||||||
relationshipName: state.relationshipName,
|
relationshipName: state.relationshipName,
|
||||||
description: state.description,
|
description: state.description,
|
||||||
connectionType: state.connectionType,
|
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 });
|
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) {
|
if (state.connectionType === "external_call" && state.externalCallConfig) {
|
||||||
try {
|
try {
|
||||||
const { ExternalCallConfigAPI } = await import("@/lib/api/externalCallConfig");
|
const { ExternalCallConfigAPI } = await import("@/lib/api/externalCallConfig");
|
||||||
|
|
|
||||||
|
|
@ -784,7 +784,21 @@ export class ButtonActionExecutor {
|
||||||
// 🔥 새로운 버튼 액션 실행 시스템 사용
|
// 🔥 새로운 버튼 액션 실행 시스템 사용
|
||||||
if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) {
|
if (config.dataflowConfig?.controlMode === "relationship" && config.dataflowConfig?.relationshipConfig) {
|
||||||
console.log("🔗 관계 기반 제어 실행:", 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 사용
|
// 새로운 ImprovedButtonActionExecutor 사용
|
||||||
const buttonConfig = {
|
const buttonConfig = {
|
||||||
actionType: config.type,
|
actionType: config.type,
|
||||||
|
|
@ -794,10 +808,10 @@ export class ButtonActionExecutor {
|
||||||
|
|
||||||
const executionResult = await ImprovedButtonActionExecutor.executeButtonAction(
|
const executionResult = await ImprovedButtonActionExecutor.executeButtonAction(
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
context.formData || {},
|
mergedFormData,
|
||||||
{
|
{
|
||||||
buttonId: context.buttonId || "unknown",
|
buttonId: context.buttonId || "unknown",
|
||||||
screenId: context.screenId || "unknown",
|
screenId: context.screenId || "unknown",
|
||||||
userId: context.userId || "unknown",
|
userId: context.userId || "unknown",
|
||||||
companyCode: context.companyCode || "*",
|
companyCode: context.companyCode || "*",
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
|
|
|
||||||
|
|
@ -645,11 +645,27 @@ export class ImprovedButtonActionExecutor {
|
||||||
formData: Record<string, any>,
|
formData: Record<string, any>,
|
||||||
context: ButtonExecutionContext
|
context: ButtonExecutionContext
|
||||||
): boolean {
|
): boolean {
|
||||||
|
console.log(`🔍 조건 평가 시작:`, {
|
||||||
|
conditions,
|
||||||
|
formDataKeys: Object.keys(formData),
|
||||||
|
formData,
|
||||||
|
contextData: context.contextData,
|
||||||
|
});
|
||||||
|
|
||||||
for (const condition of conditions) {
|
for (const condition of conditions) {
|
||||||
const fieldValue = formData[condition.field];
|
const fieldValue = formData[condition.field];
|
||||||
const conditionValue = condition.value;
|
const conditionValue = condition.value;
|
||||||
const operator = condition.operator;
|
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;
|
let conditionMet = false;
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case '=':
|
case '=':
|
||||||
|
|
@ -677,6 +693,8 @@ export class ImprovedButtonActionExecutor {
|
||||||
|
|
||||||
if (!conditionMet) {
|
if (!conditionMet) {
|
||||||
console.log(`❌ 조건 불만족: ${condition.field} ${operator} ${conditionValue} (실제값: ${fieldValue})`);
|
console.log(`❌ 조건 불만족: ${condition.field} ${operator} ${conditionValue} (실제값: ${fieldValue})`);
|
||||||
|
console.log(`❌ 사용 가능한 필드들:`, Object.keys(formData));
|
||||||
|
console.log(`❌ 전체 formData:`, formData);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue