에러 수정
This commit is contained in:
@@ -1523,7 +1523,7 @@ export const getUserInfo = async (req: AuthenticatedRequest, res: Response) => {
|
|||||||
partnerObjid: user.partner_objid,
|
partnerObjid: user.partner_objid,
|
||||||
rank: user.rank,
|
rank: user.rank,
|
||||||
photo: user.photo
|
photo: user.photo
|
||||||
? `data:image/jpeg;base64,${user.photo.toString("base64")}`
|
? `data:image/jpeg;base64,${Buffer.from(user.photo).toString("base64")}`
|
||||||
: null,
|
: null,
|
||||||
locale: user.locale,
|
locale: user.locale,
|
||||||
companyCode: user.company_code,
|
companyCode: user.company_code,
|
||||||
@@ -2415,7 +2415,7 @@ export const updateProfile = async (
|
|||||||
const responseData = {
|
const responseData = {
|
||||||
...updatedUser,
|
...updatedUser,
|
||||||
photo: updatedUser?.photo
|
photo: updatedUser?.photo
|
||||||
? `data:image/jpeg;base64,${updatedUser.photo.toString("base64")}`
|
? `data:image/jpeg;base64,${Buffer.from(updatedUser.photo).toString("base64")}`
|
||||||
: null,
|
: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ export class AuthService {
|
|||||||
authName: authNames || undefined,
|
authName: authNames || undefined,
|
||||||
companyCode: userInfo.company_code || "ILSHIN",
|
companyCode: userInfo.company_code || "ILSHIN",
|
||||||
photo: userInfo.photo
|
photo: userInfo.photo
|
||||||
? `data:image/jpeg;base64,${userInfo.photo.toString("base64")}`
|
? `data:image/jpeg;base64,${Buffer.from(userInfo.photo).toString("base64")}`
|
||||||
: undefined,
|
: undefined,
|
||||||
locale: userInfo.locale || "KR",
|
locale: userInfo.locale || "KR",
|
||||||
};
|
};
|
||||||
|
|||||||
469
제어관리_시스템_개선_계획서.md
Normal file
469
제어관리_시스템_개선_계획서.md
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
# 🔄 제어관리 시스템 개선 계획서
|
||||||
|
|
||||||
|
## 📋 개요
|
||||||
|
|
||||||
|
데이터 매핑 시스템이 추가되면서 기존 제어관리 로직과 버튼 연동 방식의 개선이 필요합니다.
|
||||||
|
|
||||||
|
## 🎯 주요 개선사항
|
||||||
|
|
||||||
|
### 1. 명칭 변경: "관계도" → "관계"
|
||||||
|
|
||||||
|
#### 1.1 변경 이유
|
||||||
|
|
||||||
|
- **기존**: "관계도"는 다이어그램 전체를 의미하는 용어
|
||||||
|
- **현재**: 실제로는 개별 "관계"를 설정하고 관리
|
||||||
|
- **개선**: 사용자 이해도 향상 및 용어 일관성 확보
|
||||||
|
|
||||||
|
#### 1.2 변경 대상 파일들
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 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 현재 문제점
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 🔴 기존: 복잡한 관계도 선택 방식
|
||||||
|
interface ButtonDataflowConfig {
|
||||||
|
controlMode: "simple" | "advanced";
|
||||||
|
selectedDiagramId?: number; // 관계도 전체 선택
|
||||||
|
selectedRelationshipId?: string; // 개별 관계 선택
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 개선 방향
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 🟢 개선: 단순화된 관계 직접 선택
|
||||||
|
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 현재 외부호출 설정 방식
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 🔴 현재: 복잡한 설정 구조
|
||||||
|
interface ExternalCallConfig {
|
||||||
|
callType: "rest-api";
|
||||||
|
restApiSettings: {
|
||||||
|
apiUrl: string;
|
||||||
|
httpMethod: string;
|
||||||
|
// ... 많은 설정들
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 개선된 연동 방식
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 🟢 개선: 단순화된 참조 구조
|
||||||
|
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 현재 실행 플로우
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[버튼 클릭] --> B[기존 액션 실행]
|
||||||
|
B --> C[제어관리 확인]
|
||||||
|
C --> D[관계도 조회]
|
||||||
|
D --> E[관계 찾기]
|
||||||
|
E --> F[조건 검증]
|
||||||
|
F --> G[액션 실행]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 개선된 실행 플로우
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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 버튼 설정 패널 개선
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 🟢 단순화된 제어 설정 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 관계 선택 컴포넌트
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
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. **변수명 정리**: `diagram` → `relationship` 관련
|
||||||
|
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 시스템 확장성
|
||||||
|
|
||||||
|
- **새로운 제어 타입 추가 용이**: 플러그인 방식으로 확장 가능
|
||||||
|
- **데이터 매핑 시스템 완전 활용**: 외부 시스템과의 유연한 연동
|
||||||
|
- **모니터링 및 로깅 강화**: 각 단계별 상세한 실행 로그
|
||||||
|
|
||||||
|
이 개선 계획을 통해 제어관리 시스템이 더욱 직관적이고 강력한 기능을 제공할 수 있을 것입니다.
|
||||||
Reference in New Issue
Block a user