|
|
|
|
@@ -903,7 +903,7 @@ export class DynamicFormService {
|
|
|
|
|
return `${key} = $${index + 1}::numeric`;
|
|
|
|
|
} else if (dataType === "boolean") {
|
|
|
|
|
return `${key} = $${index + 1}::boolean`;
|
|
|
|
|
} else if (dataType === 'jsonb' || dataType === 'json') {
|
|
|
|
|
} else if (dataType === "jsonb" || dataType === "json") {
|
|
|
|
|
// 🆕 JSONB/JSON 타입은 명시적 캐스팅
|
|
|
|
|
return `${key} = $${index + 1}::jsonb`;
|
|
|
|
|
} else {
|
|
|
|
|
@@ -917,9 +917,13 @@ export class DynamicFormService {
|
|
|
|
|
const values: any[] = Object.keys(changedFields).map((key) => {
|
|
|
|
|
const value = changedFields[key];
|
|
|
|
|
const dataType = columnTypes[key];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// JSONB/JSON 타입이고 배열/객체인 경우 JSON 문자열로 변환
|
|
|
|
|
if ((dataType === 'jsonb' || dataType === 'json') && (Array.isArray(value) || (typeof value === 'object' && value !== null))) {
|
|
|
|
|
if (
|
|
|
|
|
(dataType === "jsonb" || dataType === "json") &&
|
|
|
|
|
(Array.isArray(value) ||
|
|
|
|
|
(typeof value === "object" && value !== null))
|
|
|
|
|
) {
|
|
|
|
|
return JSON.stringify(value);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
@@ -1588,6 +1592,7 @@ export class DynamicFormService {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 제어관리 실행 (화면에 설정된 경우)
|
|
|
|
|
* 다중 제어를 순서대로 순차 실행 지원
|
|
|
|
|
*/
|
|
|
|
|
private async executeDataflowControlIfConfigured(
|
|
|
|
|
screenId: number,
|
|
|
|
|
@@ -1629,105 +1634,67 @@ export class DynamicFormService {
|
|
|
|
|
hasDataflowConfig: !!properties?.webTypeConfig?.dataflowConfig,
|
|
|
|
|
hasDiagramId:
|
|
|
|
|
!!properties?.webTypeConfig?.dataflowConfig?.selectedDiagramId,
|
|
|
|
|
hasFlowControls:
|
|
|
|
|
!!properties?.webTypeConfig?.dataflowConfig?.flowControls,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 버튼 컴포넌트이고 저장 액션이며 제어관리가 활성화된 경우
|
|
|
|
|
if (
|
|
|
|
|
properties?.componentType === "button-primary" &&
|
|
|
|
|
properties?.componentConfig?.action?.type === "save" &&
|
|
|
|
|
properties?.webTypeConfig?.enableDataflowControl === true &&
|
|
|
|
|
properties?.webTypeConfig?.dataflowConfig?.selectedDiagramId
|
|
|
|
|
properties?.webTypeConfig?.enableDataflowControl === true
|
|
|
|
|
) {
|
|
|
|
|
controlConfigFound = true;
|
|
|
|
|
const diagramId =
|
|
|
|
|
properties.webTypeConfig.dataflowConfig.selectedDiagramId;
|
|
|
|
|
const relationshipId =
|
|
|
|
|
properties.webTypeConfig.dataflowConfig.selectedRelationshipId;
|
|
|
|
|
const dataflowConfig = properties?.webTypeConfig?.dataflowConfig;
|
|
|
|
|
|
|
|
|
|
console.log(`🎯 제어관리 설정 발견:`, {
|
|
|
|
|
componentId: layout.component_id,
|
|
|
|
|
diagramId,
|
|
|
|
|
relationshipId,
|
|
|
|
|
triggerType,
|
|
|
|
|
});
|
|
|
|
|
// 다중 제어 설정 확인 (flowControls 배열)
|
|
|
|
|
const flowControls = dataflowConfig?.flowControls || [];
|
|
|
|
|
|
|
|
|
|
// 노드 플로우 실행 (relationshipId가 없는 경우 노드 플로우로 간주)
|
|
|
|
|
let controlResult: any;
|
|
|
|
|
// flowControls가 있으면 다중 제어 실행, 없으면 기존 단일 제어 실행
|
|
|
|
|
if (flowControls.length > 0) {
|
|
|
|
|
controlConfigFound = true;
|
|
|
|
|
console.log(`🎯 다중 제어관리 설정 발견: ${flowControls.length}개`);
|
|
|
|
|
|
|
|
|
|
if (!relationshipId) {
|
|
|
|
|
// 노드 플로우 실행
|
|
|
|
|
console.log(`🚀 노드 플로우 실행 (flowId: ${diagramId})`);
|
|
|
|
|
const { NodeFlowExecutionService } = await import(
|
|
|
|
|
"./nodeFlowExecutionService"
|
|
|
|
|
// 순서대로 정렬
|
|
|
|
|
const sortedControls = [...flowControls].sort(
|
|
|
|
|
(a: any, b: any) => (a.order || 0) - (b.order || 0)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const executionResult = await NodeFlowExecutionService.executeFlow(
|
|
|
|
|
// 다중 제어 순차 실행
|
|
|
|
|
await this.executeMultipleFlowControls(
|
|
|
|
|
sortedControls,
|
|
|
|
|
savedData,
|
|
|
|
|
screenId,
|
|
|
|
|
tableName,
|
|
|
|
|
triggerType,
|
|
|
|
|
userId,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
} else if (dataflowConfig?.selectedDiagramId) {
|
|
|
|
|
// 기존 단일 제어 실행 (하위 호환성)
|
|
|
|
|
controlConfigFound = true;
|
|
|
|
|
const diagramId = dataflowConfig.selectedDiagramId;
|
|
|
|
|
const relationshipId = dataflowConfig.selectedRelationshipId;
|
|
|
|
|
|
|
|
|
|
console.log(`🎯 단일 제어관리 설정 발견:`, {
|
|
|
|
|
componentId: layout.component_id,
|
|
|
|
|
diagramId,
|
|
|
|
|
{
|
|
|
|
|
sourceData: [savedData],
|
|
|
|
|
dataSourceType: "formData",
|
|
|
|
|
buttonId: "save-button",
|
|
|
|
|
screenId: screenId,
|
|
|
|
|
userId: userId,
|
|
|
|
|
companyCode: companyCode,
|
|
|
|
|
formData: savedData,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
relationshipId,
|
|
|
|
|
triggerType,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
controlResult = {
|
|
|
|
|
success: executionResult.success,
|
|
|
|
|
message: executionResult.message,
|
|
|
|
|
executedActions: executionResult.nodes?.map((node) => ({
|
|
|
|
|
nodeId: node.nodeId,
|
|
|
|
|
status: node.status,
|
|
|
|
|
duration: node.duration,
|
|
|
|
|
})),
|
|
|
|
|
errors: executionResult.nodes
|
|
|
|
|
?.filter((node) => node.status === "failed")
|
|
|
|
|
.map((node) => node.error || "실행 실패"),
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// 관계 기반 제어관리 실행
|
|
|
|
|
console.log(
|
|
|
|
|
`🎯 관계 기반 제어관리 실행 (relationshipId: ${relationshipId})`
|
|
|
|
|
await this.executeSingleFlowControl(
|
|
|
|
|
diagramId,
|
|
|
|
|
relationshipId,
|
|
|
|
|
savedData,
|
|
|
|
|
screenId,
|
|
|
|
|
tableName,
|
|
|
|
|
triggerType,
|
|
|
|
|
userId,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
controlResult =
|
|
|
|
|
await this.dataflowControlService.executeDataflowControl(
|
|
|
|
|
diagramId,
|
|
|
|
|
relationshipId,
|
|
|
|
|
triggerType,
|
|
|
|
|
savedData,
|
|
|
|
|
tableName,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`🎯 제어관리 실행 결과:`, controlResult);
|
|
|
|
|
|
|
|
|
|
if (controlResult.success) {
|
|
|
|
|
console.log(`✅ 제어관리 실행 성공: ${controlResult.message}`);
|
|
|
|
|
if (
|
|
|
|
|
controlResult.executedActions &&
|
|
|
|
|
controlResult.executedActions.length > 0
|
|
|
|
|
) {
|
|
|
|
|
console.log(`📊 실행된 액션들:`, controlResult.executedActions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 오류가 있는 경우 경고 로그 출력 (성공이지만 일부 액션 실패)
|
|
|
|
|
if (controlResult.errors && controlResult.errors.length > 0) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`⚠️ 제어관리 실행 중 일부 오류 발생:`,
|
|
|
|
|
controlResult.errors
|
|
|
|
|
);
|
|
|
|
|
// 오류 정보를 별도로 저장하여 필요시 사용자에게 알림 가능
|
|
|
|
|
// 현재는 로그만 출력하고 메인 저장 프로세스는 계속 진행
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`⚠️ 제어관리 실행 실패: ${controlResult.message}`);
|
|
|
|
|
// 제어관리 실패는 메인 저장 프로세스에 영향을 주지 않음
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 첫 번째 설정된 제어관리만 실행 (여러 개가 있을 경우)
|
|
|
|
|
// 첫 번째 설정된 버튼의 제어관리만 실행
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1741,6 +1708,218 @@ export class DynamicFormService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 다중 제어 순차 실행
|
|
|
|
|
*/
|
|
|
|
|
private async executeMultipleFlowControls(
|
|
|
|
|
flowControls: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
flowId: number;
|
|
|
|
|
flowName: string;
|
|
|
|
|
executionTiming: string;
|
|
|
|
|
order: number;
|
|
|
|
|
}>,
|
|
|
|
|
savedData: Record<string, any>,
|
|
|
|
|
screenId: number,
|
|
|
|
|
tableName: string,
|
|
|
|
|
triggerType: "insert" | "update" | "delete",
|
|
|
|
|
userId: string,
|
|
|
|
|
companyCode: string
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
console.log(`🚀 다중 제어 순차 실행 시작: ${flowControls.length}개`);
|
|
|
|
|
|
|
|
|
|
const { NodeFlowExecutionService } = await import(
|
|
|
|
|
"./nodeFlowExecutionService"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const results: Array<{
|
|
|
|
|
order: number;
|
|
|
|
|
flowId: number;
|
|
|
|
|
flowName: string;
|
|
|
|
|
success: boolean;
|
|
|
|
|
message: string;
|
|
|
|
|
duration: number;
|
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < flowControls.length; i++) {
|
|
|
|
|
const control = flowControls[i];
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`\n📍 [${i + 1}/${flowControls.length}] 제어 실행: ${control.flowName} (flowId: ${control.flowId})`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 유효하지 않은 flowId 스킵
|
|
|
|
|
if (!control.flowId || control.flowId <= 0) {
|
|
|
|
|
console.warn(`⚠️ 유효하지 않은 flowId, 스킵: ${control.flowId}`);
|
|
|
|
|
results.push({
|
|
|
|
|
order: control.order,
|
|
|
|
|
flowId: control.flowId,
|
|
|
|
|
flowName: control.flowName,
|
|
|
|
|
success: false,
|
|
|
|
|
message: "유효하지 않은 flowId",
|
|
|
|
|
duration: 0,
|
|
|
|
|
});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const executionResult = await NodeFlowExecutionService.executeFlow(
|
|
|
|
|
control.flowId,
|
|
|
|
|
{
|
|
|
|
|
sourceData: [savedData],
|
|
|
|
|
dataSourceType: "formData",
|
|
|
|
|
buttonId: "save-button",
|
|
|
|
|
screenId: screenId,
|
|
|
|
|
userId: userId,
|
|
|
|
|
companyCode: companyCode,
|
|
|
|
|
formData: savedData,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
|
order: control.order,
|
|
|
|
|
flowId: control.flowId,
|
|
|
|
|
flowName: control.flowName,
|
|
|
|
|
success: executionResult.success,
|
|
|
|
|
message: executionResult.message,
|
|
|
|
|
duration,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (executionResult.success) {
|
|
|
|
|
console.log(
|
|
|
|
|
`✅ [${i + 1}/${flowControls.length}] 제어 성공: ${control.flowName} (${duration}ms)`
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
console.error(
|
|
|
|
|
`❌ [${i + 1}/${flowControls.length}] 제어 실패: ${control.flowName} - ${executionResult.message}`
|
|
|
|
|
);
|
|
|
|
|
// 이전 제어 실패 시 다음 제어 실행 중단
|
|
|
|
|
console.warn(`⚠️ 이전 제어 실패로 인해 나머지 제어 실행 중단`);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
|
console.error(
|
|
|
|
|
`❌ [${i + 1}/${flowControls.length}] 제어 실행 오류: ${control.flowName}`,
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
|
order: control.order,
|
|
|
|
|
flowId: control.flowId,
|
|
|
|
|
flowName: control.flowName,
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || "실행 오류",
|
|
|
|
|
duration,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 오류 발생 시 다음 제어 실행 중단
|
|
|
|
|
console.warn(`⚠️ 제어 실행 오류로 인해 나머지 제어 실행 중단`);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 실행 결과 요약
|
|
|
|
|
const successCount = results.filter((r) => r.success).length;
|
|
|
|
|
const failCount = results.filter((r) => !r.success).length;
|
|
|
|
|
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
|
|
|
|
|
|
|
|
|
|
console.log(`\n📊 다중 제어 실행 완료:`, {
|
|
|
|
|
total: flowControls.length,
|
|
|
|
|
executed: results.length,
|
|
|
|
|
success: successCount,
|
|
|
|
|
failed: failCount,
|
|
|
|
|
totalDuration: `${totalDuration}ms`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 단일 제어 실행 (기존 로직, 하위 호환성)
|
|
|
|
|
*/
|
|
|
|
|
private async executeSingleFlowControl(
|
|
|
|
|
diagramId: number,
|
|
|
|
|
relationshipId: string | null,
|
|
|
|
|
savedData: Record<string, any>,
|
|
|
|
|
screenId: number,
|
|
|
|
|
tableName: string,
|
|
|
|
|
triggerType: "insert" | "update" | "delete",
|
|
|
|
|
userId: string,
|
|
|
|
|
companyCode: string
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
let controlResult: any;
|
|
|
|
|
|
|
|
|
|
if (!relationshipId) {
|
|
|
|
|
// 노드 플로우 실행
|
|
|
|
|
console.log(`🚀 노드 플로우 실행 (flowId: ${diagramId})`);
|
|
|
|
|
const { NodeFlowExecutionService } = await import(
|
|
|
|
|
"./nodeFlowExecutionService"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const executionResult = await NodeFlowExecutionService.executeFlow(
|
|
|
|
|
diagramId,
|
|
|
|
|
{
|
|
|
|
|
sourceData: [savedData],
|
|
|
|
|
dataSourceType: "formData",
|
|
|
|
|
buttonId: "save-button",
|
|
|
|
|
screenId: screenId,
|
|
|
|
|
userId: userId,
|
|
|
|
|
companyCode: companyCode,
|
|
|
|
|
formData: savedData,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
controlResult = {
|
|
|
|
|
success: executionResult.success,
|
|
|
|
|
message: executionResult.message,
|
|
|
|
|
executedActions: executionResult.nodes?.map((node) => ({
|
|
|
|
|
nodeId: node.nodeId,
|
|
|
|
|
status: node.status,
|
|
|
|
|
duration: node.duration,
|
|
|
|
|
})),
|
|
|
|
|
errors: executionResult.nodes
|
|
|
|
|
?.filter((node) => node.status === "failed")
|
|
|
|
|
.map((node) => node.error || "실행 실패"),
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// 관계 기반 제어관리 실행
|
|
|
|
|
console.log(
|
|
|
|
|
`🎯 관계 기반 제어관리 실행 (relationshipId: ${relationshipId})`
|
|
|
|
|
);
|
|
|
|
|
controlResult = await this.dataflowControlService.executeDataflowControl(
|
|
|
|
|
diagramId,
|
|
|
|
|
relationshipId,
|
|
|
|
|
triggerType,
|
|
|
|
|
savedData,
|
|
|
|
|
tableName,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`🎯 제어관리 실행 결과:`, controlResult);
|
|
|
|
|
|
|
|
|
|
if (controlResult.success) {
|
|
|
|
|
console.log(`✅ 제어관리 실행 성공: ${controlResult.message}`);
|
|
|
|
|
if (
|
|
|
|
|
controlResult.executedActions &&
|
|
|
|
|
controlResult.executedActions.length > 0
|
|
|
|
|
) {
|
|
|
|
|
console.log(`📊 실행된 액션들:`, controlResult.executedActions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (controlResult.errors && controlResult.errors.length > 0) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`⚠️ 제어관리 실행 중 일부 오류 발생:`,
|
|
|
|
|
controlResult.errors
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.warn(`⚠️ 제어관리 실행 실패: ${controlResult.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 특정 테이블의 특정 필드 값만 업데이트
|
|
|
|
|
* (다른 테이블의 레코드 업데이트 지원)
|
|
|
|
|
|