플로우 분기처리 구현
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
# 플로우 데이터 구조 설계 가이드
|
||||
|
||||
## 개요
|
||||
|
||||
플로우 관리 시스템에서 각 단계별로 테이블 구조가 다른 경우의 데이터 관리 방법
|
||||
|
||||
## 추천 아키텍처: 하이브리드 접근
|
||||
|
||||
### 1. 메인 데이터 테이블 (상태 기반)
|
||||
|
||||
각 플로우의 핵심 데이터를 담는 메인 테이블에 `flow_status` 컬럼을 추가합니다.
|
||||
|
||||
```sql
|
||||
-- 예시: 제품 수명주기 관리
|
||||
CREATE TABLE product_lifecycle (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
product_name VARCHAR(200) NOT NULL,
|
||||
flow_status VARCHAR(50) NOT NULL, -- 'purchase', 'installation', 'disposal'
|
||||
|
||||
-- 공통 필드
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by VARCHAR(50),
|
||||
|
||||
-- 단계별 핵심 정보 (NULL 허용)
|
||||
purchase_date DATE,
|
||||
purchase_price DECIMAL(15,2),
|
||||
installation_date DATE,
|
||||
installation_location VARCHAR(200),
|
||||
disposal_date DATE,
|
||||
disposal_method VARCHAR(100),
|
||||
|
||||
-- 인덱스
|
||||
INDEX idx_flow_status (flow_status),
|
||||
INDEX idx_product_code (product_code)
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 단계별 상세 정보 테이블 (선택적)
|
||||
|
||||
각 단계에서 필요한 상세 정보는 별도 테이블에 저장합니다.
|
||||
|
||||
```sql
|
||||
-- 구매 단계 상세 정보
|
||||
CREATE TABLE product_purchase_detail (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER REFERENCES product_lifecycle(id),
|
||||
vendor_name VARCHAR(200),
|
||||
vendor_contact VARCHAR(100),
|
||||
purchase_order_no VARCHAR(50),
|
||||
warranty_period INTEGER, -- 월 단위
|
||||
warranty_end_date DATE,
|
||||
specifications JSONB, -- 유연한 사양 정보
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 설치 단계 상세 정보
|
||||
CREATE TABLE product_installation_detail (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER REFERENCES product_lifecycle(id),
|
||||
technician_name VARCHAR(100),
|
||||
installation_address TEXT,
|
||||
installation_notes TEXT,
|
||||
installation_photos JSONB, -- [{url, description}]
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 폐기 단계 상세 정보
|
||||
CREATE TABLE product_disposal_detail (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER REFERENCES product_lifecycle(id),
|
||||
disposal_company VARCHAR(200),
|
||||
disposal_certificate_no VARCHAR(100),
|
||||
environmental_compliance BOOLEAN,
|
||||
disposal_cost DECIMAL(15,2),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 3. 플로우 단계 설정 테이블 수정
|
||||
|
||||
`flow_step` 테이블에 단계별 필드 매핑 정보를 추가합니다.
|
||||
|
||||
```sql
|
||||
ALTER TABLE flow_step
|
||||
ADD COLUMN status_value VARCHAR(50), -- 이 단계의 상태값
|
||||
ADD COLUMN required_fields JSONB, -- 필수 입력 필드 목록
|
||||
ADD COLUMN detail_table_name VARCHAR(200), -- 상세 정보 테이블명 (선택적)
|
||||
ADD COLUMN field_mappings JSONB; -- 메인 테이블과 상세 테이블 필드 매핑
|
||||
|
||||
-- 예시 데이터
|
||||
INSERT INTO flow_step (flow_definition_id, step_name, step_order, table_name, status_value, required_fields, detail_table_name) VALUES
|
||||
(1, '구매', 1, 'product_lifecycle', 'purchase',
|
||||
'["product_code", "product_name", "purchase_date", "purchase_price"]'::jsonb,
|
||||
'product_purchase_detail'),
|
||||
(1, '설치', 2, 'product_lifecycle', 'installation',
|
||||
'["installation_date", "installation_location"]'::jsonb,
|
||||
'product_installation_detail'),
|
||||
(1, '폐기', 3, 'product_lifecycle', 'disposal',
|
||||
'["disposal_date", "disposal_method"]'::jsonb,
|
||||
'product_disposal_detail');
|
||||
```
|
||||
|
||||
## 데이터 이동 로직
|
||||
|
||||
### 백엔드 서비스 수정
|
||||
|
||||
```typescript
|
||||
// backend-node/src/services/flowDataMoveService.ts
|
||||
|
||||
export class FlowDataMoveService {
|
||||
/**
|
||||
* 다음 단계로 데이터 이동
|
||||
*/
|
||||
async moveToNextStep(
|
||||
flowId: number,
|
||||
currentStepId: number,
|
||||
nextStepId: number,
|
||||
dataId: any
|
||||
): Promise<boolean> {
|
||||
const client = await db.getClient();
|
||||
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
|
||||
// 1. 현재 단계와 다음 단계 정보 조회
|
||||
const currentStep = await this.getStepInfo(currentStepId);
|
||||
const nextStep = await this.getStepInfo(nextStepId);
|
||||
|
||||
if (!currentStep || !nextStep) {
|
||||
throw new Error("유효하지 않은 단계입니다");
|
||||
}
|
||||
|
||||
// 2. 메인 테이블의 상태 업데이트
|
||||
const updateQuery = `
|
||||
UPDATE ${currentStep.table_name}
|
||||
SET flow_status = $1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
AND flow_status = $3
|
||||
`;
|
||||
|
||||
const result = await client.query(updateQuery, [
|
||||
nextStep.status_value,
|
||||
dataId,
|
||||
currentStep.status_value,
|
||||
]);
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
throw new Error("데이터를 찾을 수 없거나 이미 이동되었습니다");
|
||||
}
|
||||
|
||||
// 3. 감사 로그 기록
|
||||
await this.logDataMove(client, {
|
||||
flowId,
|
||||
fromStepId: currentStepId,
|
||||
toStepId: nextStepId,
|
||||
dataId,
|
||||
tableName: currentStep.table_name,
|
||||
statusFrom: currentStep.status_value,
|
||||
statusTo: nextStep.status_value,
|
||||
});
|
||||
|
||||
await client.query("COMMIT");
|
||||
return true;
|
||||
} catch (error) {
|
||||
await client.query("ROLLBACK");
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
private async getStepInfo(stepId: number) {
|
||||
const query = `
|
||||
SELECT id, table_name, status_value, detail_table_name, required_fields
|
||||
FROM flow_step
|
||||
WHERE id = $1
|
||||
`;
|
||||
const result = await db.query(query, [stepId]);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
private async logDataMove(client: any, params: any) {
|
||||
const query = `
|
||||
INSERT INTO flow_audit_log (
|
||||
flow_definition_id, from_step_id, to_step_id,
|
||||
data_id, table_name, status_from, status_to,
|
||||
moved_at, moved_by
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), 'system')
|
||||
`;
|
||||
|
||||
await client.query(query, [
|
||||
params.flowId,
|
||||
params.fromStepId,
|
||||
params.toStepId,
|
||||
params.dataId,
|
||||
params.tableName,
|
||||
params.statusFrom,
|
||||
params.statusTo,
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 플로우 조건 설정
|
||||
|
||||
각 단계의 조건은 `flow_status` 컬럼을 기준으로 설정합니다:
|
||||
|
||||
```json
|
||||
// 구매 단계 조건
|
||||
{
|
||||
"operator": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"column": "flow_status",
|
||||
"operator": "=",
|
||||
"value": "purchase"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 설치 단계 조건
|
||||
{
|
||||
"operator": "AND",
|
||||
"conditions": [
|
||||
{
|
||||
"column": "flow_status",
|
||||
"operator": "=",
|
||||
"value": "installation"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 프론트엔드 구현
|
||||
|
||||
### 단계별 폼 렌더링
|
||||
|
||||
각 단계에서 필요한 필드를 동적으로 렌더링합니다.
|
||||
|
||||
```typescript
|
||||
// 단계 정보에서 필수 필드 가져오기
|
||||
const requiredFields = step.required_fields; // ["purchase_date", "purchase_price"]
|
||||
|
||||
// 동적 폼 생성
|
||||
{
|
||||
requiredFields.map((fieldName) => (
|
||||
<FormField
|
||||
key={fieldName}
|
||||
name={fieldName}
|
||||
label={getFieldLabel(fieldName)}
|
||||
type={getFieldType(fieldName)}
|
||||
required={true}
|
||||
/>
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
## 장점
|
||||
|
||||
1. **단순한 데이터 이동**: 상태값만 업데이트
|
||||
2. **유연한 구조**: 단계별 상세 정보는 별도 테이블
|
||||
3. **완벽한 이력 추적**: 감사 로그로 모든 이동 기록
|
||||
4. **쿼리 효율**: 단일 테이블 조회로 각 단계 데이터 확인
|
||||
5. **확장성**: 새로운 단계 추가 시 컬럼 추가 또는 상세 테이블 생성
|
||||
|
||||
## 마이그레이션 스크립트
|
||||
|
||||
```sql
|
||||
-- 1. 기존 테이블에 flow_status 컬럼 추가
|
||||
ALTER TABLE product_lifecycle
|
||||
ADD COLUMN flow_status VARCHAR(50) DEFAULT 'purchase';
|
||||
|
||||
-- 2. 인덱스 생성
|
||||
CREATE INDEX idx_product_lifecycle_status ON product_lifecycle(flow_status);
|
||||
|
||||
-- 3. flow_step 테이블 확장
|
||||
ALTER TABLE flow_step
|
||||
ADD COLUMN status_value VARCHAR(50),
|
||||
ADD COLUMN required_fields JSONB,
|
||||
ADD COLUMN detail_table_name VARCHAR(200);
|
||||
|
||||
-- 4. 기존 데이터 마이그레이션
|
||||
UPDATE flow_step
|
||||
SET status_value = CASE step_order
|
||||
WHEN 1 THEN 'purchase'
|
||||
WHEN 2 THEN 'installation'
|
||||
WHEN 3 THEN 'disposal'
|
||||
END
|
||||
WHERE flow_definition_id = 1;
|
||||
```
|
||||
|
||||
## 결론
|
||||
|
||||
이 하이브리드 접근 방식을 사용하면:
|
||||
|
||||
- 각 단계의 데이터는 같은 메인 테이블에서 `flow_status`로 구분
|
||||
- 단계별 추가 정보는 별도 상세 테이블에 저장 (선택적)
|
||||
- 데이터 이동은 상태값 업데이트만으로 간단하게 처리
|
||||
- 완전한 감사 로그와 이력 추적 가능
|
||||
@@ -0,0 +1,381 @@
|
||||
# 플로우 하이브리드 모드 사용 가이드
|
||||
|
||||
## 개요
|
||||
|
||||
플로우 관리 시스템은 세 가지 데이터 이동 방식을 지원합니다:
|
||||
|
||||
1. **상태 변경 방식(status)**: 같은 테이블 내에서 상태 컬럼만 업데이트
|
||||
2. **테이블 이동 방식(table)**: 완전히 다른 테이블로 데이터 복사 및 이동
|
||||
3. **하이브리드 방식(both)**: 두 가지 모두 수행
|
||||
|
||||
## 1. 상태 변경 방식 (Status Mode)
|
||||
|
||||
### 사용 시나리오
|
||||
|
||||
- 같은 엔티티가 여러 단계를 거치는 경우
|
||||
- 예: 승인 프로세스 (대기 → 검토 → 승인 → 완료)
|
||||
|
||||
### 설정 방법
|
||||
|
||||
```sql
|
||||
-- 플로우 정의 생성
|
||||
INSERT INTO flow_definition (name, description, table_name, is_active)
|
||||
VALUES ('문서 승인', '문서 승인 프로세스', 'documents', true);
|
||||
|
||||
-- 단계 생성 (상태 변경 방식)
|
||||
INSERT INTO flow_step (
|
||||
flow_definition_id, step_name, step_order,
|
||||
table_name, move_type, status_column, status_value,
|
||||
condition_json
|
||||
) VALUES
|
||||
(1, '대기', 1, 'documents', 'status', 'approval_status', 'pending',
|
||||
'{"operator":"AND","conditions":[{"column":"approval_status","operator":"=","value":"pending"}]}'::jsonb),
|
||||
|
||||
(1, '검토중', 2, 'documents', 'status', 'approval_status', 'reviewing',
|
||||
'{"operator":"AND","conditions":[{"column":"approval_status","operator":"=","value":"reviewing"}]}'::jsonb),
|
||||
|
||||
(1, '승인됨', 3, 'documents', 'status', 'approval_status', 'approved',
|
||||
'{"operator":"AND","conditions":[{"column":"approval_status","operator":"=","value":"approved"}]}'::jsonb);
|
||||
```
|
||||
|
||||
### 테이블 구조
|
||||
|
||||
```sql
|
||||
CREATE TABLE documents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(200),
|
||||
content TEXT,
|
||||
approval_status VARCHAR(50) DEFAULT 'pending', -- 상태 컬럼
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 데이터 이동
|
||||
|
||||
```typescript
|
||||
// 프론트엔드에서 호출
|
||||
await moveData(flowId, currentStepId, nextStepId, documentId);
|
||||
|
||||
// 백엔드에서 처리
|
||||
// documents 테이블의 approval_status가 'pending' → 'reviewing'으로 변경됨
|
||||
```
|
||||
|
||||
## 2. 테이블 이동 방식 (Table Mode)
|
||||
|
||||
### 사용 시나리오
|
||||
|
||||
- 완전히 다른 엔티티를 다루는 경우
|
||||
- 예: 제품 수명주기 (구매 주문 → 설치 작업 → 폐기 신청)
|
||||
|
||||
### 설정 방법
|
||||
|
||||
```sql
|
||||
-- 플로우 정의 생성
|
||||
INSERT INTO flow_definition (name, description, table_name, is_active)
|
||||
VALUES ('제품 수명주기', '구매→설치→폐기 프로세스', 'purchase_orders', true);
|
||||
|
||||
-- 단계 생성 (테이블 이동 방식)
|
||||
INSERT INTO flow_step (
|
||||
flow_definition_id, step_name, step_order,
|
||||
table_name, move_type, target_table,
|
||||
field_mappings, required_fields
|
||||
) VALUES
|
||||
(2, '구매', 1, 'purchase_orders', 'table', 'installations',
|
||||
'{"order_id":"purchase_order_id","product_name":"product_name","product_code":"product_code"}'::jsonb,
|
||||
'["product_name","purchase_date","purchase_price"]'::jsonb),
|
||||
|
||||
(2, '설치', 2, 'installations', 'table', 'disposals',
|
||||
'{"installation_id":"installation_id","product_name":"product_name","product_code":"product_code"}'::jsonb,
|
||||
'["installation_date","installation_location","technician"]'::jsonb),
|
||||
|
||||
(2, '폐기', 3, 'disposals', 'table', NULL,
|
||||
NULL,
|
||||
'["disposal_date","disposal_method","disposal_cost"]'::jsonb);
|
||||
```
|
||||
|
||||
### 테이블 구조
|
||||
|
||||
```sql
|
||||
-- 단계 1: 구매 주문 테이블
|
||||
CREATE TABLE purchase_orders (
|
||||
id SERIAL PRIMARY KEY,
|
||||
order_id VARCHAR(50) UNIQUE,
|
||||
product_name VARCHAR(200),
|
||||
product_code VARCHAR(50),
|
||||
purchase_date DATE,
|
||||
purchase_price DECIMAL(15,2),
|
||||
vendor_name VARCHAR(200),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 단계 2: 설치 작업 테이블
|
||||
CREATE TABLE installations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
purchase_order_id VARCHAR(50), -- 매핑 필드
|
||||
product_name VARCHAR(200),
|
||||
product_code VARCHAR(50),
|
||||
installation_date DATE,
|
||||
installation_location TEXT,
|
||||
technician VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 단계 3: 폐기 신청 테이블
|
||||
CREATE TABLE disposals (
|
||||
id SERIAL PRIMARY KEY,
|
||||
installation_id INTEGER, -- 매핑 필드
|
||||
product_name VARCHAR(200),
|
||||
product_code VARCHAR(50),
|
||||
disposal_date DATE,
|
||||
disposal_method VARCHAR(100),
|
||||
disposal_cost DECIMAL(15,2),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 데이터 이동
|
||||
|
||||
```typescript
|
||||
// 구매 → 설치 단계로 이동
|
||||
const result = await moveData(
|
||||
flowId,
|
||||
purchaseStepId,
|
||||
installationStepId,
|
||||
purchaseOrderId
|
||||
);
|
||||
|
||||
// 결과:
|
||||
// 1. purchase_orders 테이블에서 데이터 조회
|
||||
// 2. field_mappings에 따라 필드 매핑
|
||||
// 3. installations 테이블에 새 레코드 생성
|
||||
// 4. flow_data_mapping 테이블에 매핑 정보 저장
|
||||
// 5. flow_audit_log에 이동 이력 기록
|
||||
```
|
||||
|
||||
### 매핑 정보 조회
|
||||
|
||||
```sql
|
||||
-- 플로우 전체 이력 조회
|
||||
SELECT * FROM flow_data_mapping
|
||||
WHERE flow_definition_id = 2;
|
||||
|
||||
-- 결과 예시:
|
||||
-- {
|
||||
-- "current_step_id": 2,
|
||||
-- "step_data_map": {
|
||||
-- "1": "123", -- 구매 주문 ID
|
||||
-- "2": "456" -- 설치 작업 ID
|
||||
-- }
|
||||
-- }
|
||||
```
|
||||
|
||||
## 3. 하이브리드 방식 (Both Mode)
|
||||
|
||||
### 사용 시나리오
|
||||
|
||||
- 상태도 변경하면서 다른 테이블로도 이동해야 하는 경우
|
||||
- 예: 검토 완료 후 승인 테이블로 이동하면서 원본 테이블의 상태도 변경
|
||||
|
||||
### 설정 방법
|
||||
|
||||
```sql
|
||||
INSERT INTO flow_step (
|
||||
flow_definition_id, step_name, step_order,
|
||||
table_name, move_type,
|
||||
status_column, status_value, -- 상태 변경용
|
||||
target_table, field_mappings, -- 테이블 이동용
|
||||
required_fields
|
||||
) VALUES
|
||||
(3, '검토 완료', 1, 'review_queue', 'both',
|
||||
'status', 'reviewed',
|
||||
'approved_items',
|
||||
'{"item_id":"source_item_id","item_name":"name","review_score":"score"}'::jsonb,
|
||||
'["review_date","reviewer_id","review_comment"]'::jsonb);
|
||||
```
|
||||
|
||||
### 동작
|
||||
|
||||
1. **상태 변경**: review_queue 테이블의 status를 'reviewed'로 업데이트
|
||||
2. **테이블 이동**: approved_items 테이블에 새 레코드 생성
|
||||
3. **매핑 저장**: flow_data_mapping에 양쪽 ID 기록
|
||||
|
||||
## 4. 프론트엔드 구현
|
||||
|
||||
### FlowWidget에서 데이터 이동
|
||||
|
||||
```typescript
|
||||
// frontend/components/screen/widgets/FlowWidget.tsx
|
||||
|
||||
const handleMoveToNext = async () => {
|
||||
// ... 선택된 데이터 준비 ...
|
||||
|
||||
for (const data of selectedData) {
|
||||
// Primary Key 추출 (첫 번째 컬럼 또는 'id' 컬럼)
|
||||
const dataId = data.id || data[stepDataColumns[0]];
|
||||
|
||||
// API 호출
|
||||
const response = await moveData(flowId, currentStepId, nextStepId, dataId);
|
||||
|
||||
if (!response.success) {
|
||||
toast.error(`이동 실패: ${response.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 성공 시 targetDataId 확인 가능
|
||||
if (response.data?.targetDataId) {
|
||||
console.log(`새 테이블 ID: ${response.data.targetDataId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 데이터 새로고침
|
||||
await refreshStepData();
|
||||
};
|
||||
```
|
||||
|
||||
### 추가 데이터 전달
|
||||
|
||||
```typescript
|
||||
// 다음 단계로 이동하면서 추가 데이터 입력
|
||||
const additionalData = {
|
||||
installation_date: "2025-10-20",
|
||||
technician: "John Doe",
|
||||
installation_notes: "Installed successfully",
|
||||
};
|
||||
|
||||
const response = await fetch(`/api/flow/${flowId}/move`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
fromStepId: currentStepId,
|
||||
toStepId: nextStepId,
|
||||
dataId: dataId,
|
||||
additionalData: additionalData,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## 5. 감사 로그 조회
|
||||
|
||||
### 특정 데이터의 이력 조회
|
||||
|
||||
```typescript
|
||||
const auditLogs = await getFlowAuditLogs(flowId, dataId);
|
||||
|
||||
// 결과:
|
||||
[
|
||||
{
|
||||
id: 1,
|
||||
moveType: "table",
|
||||
sourceTable: "purchase_orders",
|
||||
targetTable: "installations",
|
||||
sourceDataId: "123",
|
||||
targetDataId: "456",
|
||||
fromStepName: "구매",
|
||||
toStepName: "설치",
|
||||
changedBy: "system",
|
||||
changedAt: "2025-10-20T10:30:00",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
moveType: "table",
|
||||
sourceTable: "installations",
|
||||
targetTable: "disposals",
|
||||
sourceDataId: "456",
|
||||
targetDataId: "789",
|
||||
fromStepName: "설치",
|
||||
toStepName: "폐기",
|
||||
changedBy: "user123",
|
||||
changedAt: "2025-10-21T14:20:00",
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
## 6. 모범 사례
|
||||
|
||||
### 상태 변경 방식 사용 시
|
||||
|
||||
✅ **권장**:
|
||||
|
||||
- 단일 엔티티의 생명주기 관리
|
||||
- 간단한 승인 프로세스
|
||||
- 빠른 상태 조회가 필요한 경우
|
||||
|
||||
❌ **비권장**:
|
||||
|
||||
- 각 단계마다 완전히 다른 데이터 구조가 필요한 경우
|
||||
|
||||
### 테이블 이동 방식 사용 시
|
||||
|
||||
✅ **권장**:
|
||||
|
||||
- 각 단계가 독립적인 엔티티
|
||||
- 단계별로 다른 팀/부서에서 관리
|
||||
- 각 단계의 데이터 구조가 완전히 다른 경우
|
||||
|
||||
❌ **비권장**:
|
||||
|
||||
- 단순한 상태 변경만 필요한 경우 (오버엔지니어링)
|
||||
- 실시간 조회 성능이 중요한 경우 (JOIN 비용)
|
||||
|
||||
### 하이브리드 방식 사용 시
|
||||
|
||||
✅ **권장**:
|
||||
|
||||
- 원본 데이터는 보존하면서 처리된 데이터는 별도 저장
|
||||
- 이중 추적이 필요한 경우
|
||||
|
||||
## 7. 주의사항
|
||||
|
||||
1. **필드 매핑 주의**: `field_mappings`의 소스/타겟 필드가 정확해야 함
|
||||
2. **필수 필드 검증**: `required_fields`에 명시된 필드는 반드시 입력
|
||||
3. **트랜잭션**: 모든 이동은 트랜잭션으로 처리되어 원자성 보장
|
||||
4. **Primary Key**: 테이블 이동 시 소스 데이터의 Primary Key가 명확해야 함
|
||||
5. **순환 참조 방지**: 플로우 연결 시 사이클이 발생하지 않도록 주의
|
||||
|
||||
## 8. 트러블슈팅
|
||||
|
||||
### Q1: "데이터를 찾을 수 없습니다" 오류
|
||||
|
||||
- 원인: Primary Key가 잘못되었거나 데이터가 이미 이동됨
|
||||
- 해결: `flow_audit_log`에서 이동 이력 확인
|
||||
|
||||
### Q2: "매핑할 데이터가 없습니다" 오류
|
||||
|
||||
- 원인: `field_mappings`가 비어있거나 소스 필드가 없음
|
||||
- 해결: 소스 테이블에 매핑 필드가 존재하는지 확인
|
||||
|
||||
### Q3: 테이블 이동 후 원본 데이터 처리
|
||||
|
||||
- 원본 데이터는 자동으로 삭제되지 않음
|
||||
- 필요시 별도 로직으로 처리하거나 `is_archived` 플래그 사용
|
||||
|
||||
## 9. 성능 최적화
|
||||
|
||||
1. **인덱스 생성**: 상태 컬럼에 인덱스 필수
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_documents_status ON documents(approval_status);
|
||||
```
|
||||
|
||||
2. **배치 이동**: 대량 데이터는 배치 API 사용
|
||||
|
||||
```typescript
|
||||
await moveBatchData(flowId, fromStepId, toStepId, dataIds);
|
||||
```
|
||||
|
||||
3. **매핑 테이블 정리**: 주기적으로 완료된 플로우의 매핑 데이터 아카이빙
|
||||
|
||||
```sql
|
||||
DELETE FROM flow_data_mapping
|
||||
WHERE created_at < NOW() - INTERVAL '1 year'
|
||||
AND current_step_id IN (SELECT id FROM flow_step WHERE step_order = (SELECT MAX(step_order) FROM flow_step WHERE flow_definition_id = ?));
|
||||
```
|
||||
|
||||
## 결론
|
||||
|
||||
하이브리드 플로우 시스템은 다양한 비즈니스 요구사항에 유연하게 대응할 수 있습니다:
|
||||
|
||||
- 간단한 상태 관리부터
|
||||
- 복잡한 다단계 프로세스까지
|
||||
- 하나의 시스템으로 통합 관리 가능
|
||||
Reference in New Issue
Block a user