ERP-node/제어관리_시스템_개선_계획서.md

13 KiB

🔄 제어관리 시스템 개선 계획서

📋 개요

데이터 매핑 시스템이 추가되면서 기존 제어관리 로직과 버튼 연동 방식의 개선이 필요합니다.

🎯 주요 개선사항

1. 명칭 변경: "관계도" → "관계"

1.1 변경 이유

  • 기존: "관계도"는 다이어그램 전체를 의미하는 용어
  • 현재: 실제로는 개별 "관계"를 설정하고 관리
  • 개선: 사용자 이해도 향상 및 용어 일관성 확보

1.2 변경 대상 파일들

// UI 컴포넌트들
frontend / components / screen / config -
  panels / ButtonDataflowConfigPanel.tsx;
frontend / components / dataflow / DataFlowDesigner.tsx;
frontend / components / dataflow / SaveDiagramModal.tsx;
frontend / components / dataflow / RelationshipListModal.tsx;
frontend /
  components /
  dataflow /
  connection /
  redesigned /
  RightPanel /
  ConnectionStep.tsx;

// API 및 서비스
frontend / lib / api / dataflow.ts;
frontend / hooks / useDataFlowDesigner.ts;

// 타입 정의
frontend / types / control - management.ts;

2. 버튼 제어관리 로직 개선

2.1 현재 문제점

// 🔴 기존: 복잡한 관계도 선택 방식
interface ButtonDataflowConfig {
  controlMode: "simple" | "advanced";
  selectedDiagramId?: number; // 관계도 전체 선택
  selectedRelationshipId?: string; // 개별 관계 선택
  // ...
}

2.2 개선 방향

// 🟢 개선: 단순화된 관계 직접 선택
interface ButtonDataflowConfig {
  controlMode: "relationship" | "external_call" | "custom";

  // 관계 기반 제어
  relationshipConfig?: {
    relationshipId: string; // 관계 직접 선택
    relationshipName: string; // 관계명 표시
    executionTiming: "before" | "after" | "replace";
    contextData?: Record<string, any>; // 실행 시 전달할 컨텍스트
  };

  // 외부호출 제어
  externalCallConfig?: {
    configId: string; // external_call_configs ID
    configName: string; // 설정명 표시
    executionTiming: "before" | "after" | "replace";
    dataMappingEnabled: boolean; // 데이터 매핑 사용 여부
  };

  // 커스텀 제어
  customConfig?: {
    actionType: string;
    parameters: Record<string, any>;
  };
}

3. 외부호출 연동 개선

3.1 현재 외부호출 설정 방식

// 🔴 현재: 복잡한 설정 구조
interface ExternalCallConfig {
  callType: "rest-api";
  restApiSettings: {
    apiUrl: string;
    httpMethod: string;
    // ... 많은 설정들
  };
}

3.2 개선된 연동 방식

// 🟢 개선: 단순화된 참조 구조
interface ButtonExternalCallConfig {
  // 1단계: 저장된 외부호출 설정 선택
  externalCallConfigId: string; // external_call_configs 테이블 ID
  configName: string; // 설정명 (UI 표시용)

  // 2단계: 실행 시점 설정
  executionTiming: "before" | "after" | "replace";

  // 3단계: 데이터 전달 방식
  dataMapping: {
    enabled: boolean; // 데이터 매핑 사용 여부
    sourceMode: "form" | "table" | "custom"; // 데이터 소스
    sourceConfig?: {
      tableName?: string; // table 모드용
      customData?: Record<string, any>; // custom 모드용
    };
  };

  // 4단계: 실행 옵션
  executionOptions: {
    rollbackOnError: boolean; // 실패 시 롤백
    showLoadingIndicator: boolean; // 로딩 표시
    successMessage?: string; // 성공 메시지
    errorMessage?: string; // 실패 메시지
  };
}

4. 버튼 액션 실행 로직 개선

4.1 현재 실행 플로우

graph TD
    A[버튼 클릭] --> B[기존 액션 실행]
    B --> C[제어관리 확인]
    C --> D[관계도 조회]
    D --> E[관계 찾기]
    E --> F[조건 검증]
    F --> G[액션 실행]

4.2 개선된 실행 플로우

graph TD
    A[버튼 클릭] --> B[제어 설정 확인]
    B --> C{제어 타입}
    C -->|관계| D[관계 직접 실행]
    C -->|외부호출| E[외부호출 실행]
    C -->|없음| F[기존 액션만 실행]

    D --> G[조건 검증]
    G --> H[관계 액션 실행]

    E --> I[데이터 매핑]
    I --> J[외부 API 호출]
    J --> K[응답 처리]

    H --> L[완료]
    K --> L
    F --> L

4.3 개선된 ButtonActionExecutor

export class ButtonActionExecutor {
  /**
   * 🔥 개선된 버튼 액션 실행
   */
  static async executeButtonAction(
    buttonConfig: ExtendedButtonTypeConfig,
    formData: Record<string, any>,
    context: ButtonExecutionContext
  ): Promise<ButtonExecutionResult> {
    const executionPlan = this.createExecutionPlan(buttonConfig);
    const results: ExecutionResult[] = [];

    try {
      // 1. Before 타이밍 제어 실행
      if (executionPlan.beforeControls.length > 0) {
        const beforeResults = await this.executeControls(
          executionPlan.beforeControls,
          formData,
          context
        );
        results.push(...beforeResults);
      }

      // 2. 메인 액션 실행 (replace가 아닌 경우에만)
      if (!executionPlan.hasReplaceControl) {
        const mainResult = await this.executeMainAction(
          buttonConfig,
          formData,
          context
        );
        results.push(mainResult);
      }

      // 3. After 타이밍 제어 실행
      if (executionPlan.afterControls.length > 0) {
        const afterResults = await this.executeControls(
          executionPlan.afterControls,
          formData,
          context
        );
        results.push(...afterResults);
      }

      return {
        success: true,
        results,
        executionTime: Date.now() - context.startTime,
      };
    } catch (error) {
      // 롤백 처리
      await this.handleExecutionError(error, results, buttonConfig);
      throw error;
    }
  }

  /**
   * 🔥 제어 실행 (관계 또는 외부호출)
   */
  private static async executeControls(
    controls: ControlConfig[],
    formData: Record<string, any>,
    context: ButtonExecutionContext
  ): Promise<ExecutionResult[]> {
    const results: ExecutionResult[] = [];

    for (const control of controls) {
      switch (control.type) {
        case "relationship":
          const relationshipResult = await this.executeRelationship(
            control.relationshipConfig!,
            formData,
            context
          );
          results.push(relationshipResult);
          break;

        case "external_call":
          const externalCallResult = await this.executeExternalCall(
            control.externalCallConfig!,
            formData,
            context
          );
          results.push(externalCallResult);
          break;
      }
    }

    return results;
  }

  /**
   * 🔥 관계 실행
   */
  private static async executeRelationship(
    config: RelationshipConfig,
    formData: Record<string, any>,
    context: ButtonExecutionContext
  ): Promise<ExecutionResult> {
    // 1. 관계 정보 조회
    const relationship = await RelationshipAPI.getRelationshipById(
      config.relationshipId
    );

    // 2. 컨텍스트 데이터 준비
    const contextData = {
      ...formData,
      ...config.contextData,
      buttonId: context.buttonId,
      screenId: context.screenId,
      userId: context.userId,
      companyCode: context.companyCode,
    };

    // 3. 관계 실행
    return await EventTriggerService.executeSpecificRelationship(
      relationship,
      contextData,
      context.companyCode
    );
  }

  /**
   * 🔥 외부호출 실행
   */
  private static async executeExternalCall(
    config: ExternalCallConfig,
    formData: Record<string, any>,
    context: ButtonExecutionContext
  ): Promise<ExecutionResult> {
    // 1. 외부호출 설정 조회
    const externalCallConfig = await ExternalCallConfigAPI.getConfigById(
      config.configId
    );

    // 2. 데이터 매핑 처리
    let mappedData = formData;
    if (config.dataMappingEnabled && externalCallConfig.dataMappingConfig) {
      mappedData = await DataMappingService.processOutboundData(
        externalCallConfig.dataMappingConfig.outboundMapping,
        formData
      );
    }

    // 3. 외부 API 호출
    const callResult = await ExternalCallService.executeWithDataMapping(
      externalCallConfig.configData,
      externalCallConfig.dataMappingConfig,
      mappedData
    );

    // 4. 응답 데이터 처리 (Inbound 매핑)
    if (
      callResult.success &&
      config.dataMappingEnabled &&
      externalCallConfig.dataMappingConfig?.direction === "inbound"
    ) {
      await DataMappingService.processInboundData(
        callResult.response,
        externalCallConfig.dataMappingConfig.inboundMapping!
      );
    }

    return {
      success: callResult.success,
      message: callResult.success ? "외부호출 성공" : callResult.error,
      executionTime: callResult.executionTime,
      data: callResult,
    };
  }
}

5. UI/UX 개선 사항

5.1 버튼 설정 패널 개선

// 🟢 단순화된 제어 설정 UI
const ButtonControlConfigPanel = () => {
  return (
    <Card>
      <CardHeader>
        <CardTitle>버튼 제어 설정</CardTitle>
      </CardHeader>
      <CardContent>
        <Tabs value={controlType} onValueChange={setControlType}>
          <TabsList>
            <TabsTrigger value="none">제어 없음</TabsTrigger>
            <TabsTrigger value="relationship">관계 실행</TabsTrigger>
            <TabsTrigger value="external_call">외부 호출</TabsTrigger>
          </TabsList>

          <TabsContent value="relationship">
            <RelationshipSelector
              selectedRelationshipId={config.relationshipId}
              onSelect={handleRelationshipSelect}
            />
          </TabsContent>

          <TabsContent value="external_call">
            <ExternalCallSelector
              selectedConfigId={config.externalCallConfigId}
              onSelect={handleExternalCallSelect}
            />
          </TabsContent>
        </Tabs>
      </CardContent>
    </Card>
  );
};

5.2 관계 선택 컴포넌트

const RelationshipSelector = ({ onSelect }) => {
  const [relationships, setRelationships] = useState([]);

  useEffect(() => {
    // 전체 관계 목록 로드 (관계도별 구분 없이)
    loadAllRelationships();
  }, []);

  return (
    <div className="space-y-4">
      <Label>실행할 관계 선택</Label>
      <Select onValueChange={onSelect}>
        <SelectTrigger>
          <SelectValue placeholder="관계를 선택하세요" />
        </SelectTrigger>
        <SelectContent>
          {relationships.map((rel) => (
            <SelectItem key={rel.id} value={rel.id}>
              <div className="flex flex-col">
                <span className="font-medium">{rel.name}</span>
                <span className="text-xs text-muted-foreground">
                  {rel.sourceTable}  {rel.targetTable}
                </span>
              </div>
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
};

6. 구현 우선순위

Phase 1: 명칭 변경 (1일)

  1. UI 텍스트 변경: "관계도" → "관계"
  2. 변수명 정리: diagramrelationship 관련
  3. API 엔드포인트 정리: 일관성 있는 명명

Phase 2: 버튼 제어 로직 개선 (2-3일)

  1. ButtonDataflowConfig 타입 개선
  2. RelationshipSelector 컴포넌트 개발
  3. ExternalCallSelector 컴포넌트 개발
  4. ButtonActionExecutor 로직 개선

Phase 3: 외부호출 통합 (1-2일)

  1. 외부호출 설정 참조 방식 개선
  2. 데이터 매핑 통합
  3. 실행 플로우 최적화

Phase 4: 테스트 및 최적화 (1일)

  1. 전체 플로우 테스트
  2. 성능 최적화
  3. 사용자 가이드 업데이트

7. 기대 효과

7.1 사용자 경험 개선

  • 직관적인 용어: "관계도" → "관계"로 이해도 향상
  • 단순화된 설정: 복잡한 관계도 탐색 → 직접 관계 선택
  • 통합된 제어: 관계 실행과 외부호출을 동일한 방식으로 관리

7.2 개발 효율성 향상

  • 명확한 책임 분리: 관계 관리와 외부호출 관리 분리
  • 재사용성 증대: 외부호출 설정의 재사용성 향상
  • 유지보수성 개선: 단순화된 로직으로 디버깅 용이

7.3 시스템 확장성

  • 새로운 제어 타입 추가 용이: 플러그인 방식으로 확장 가능
  • 데이터 매핑 시스템 완전 활용: 외부 시스템과의 유연한 연동
  • 모니터링 및 로깅 강화: 각 단계별 상세한 실행 로그

이 개선 계획을 통해 제어관리 시스템이 더욱 직관적이고 강력한 기능을 제공할 수 있을 것입니다.