# 플로우 관리 시스템 구현 계획서 ## 1. 개요 ### 1.1 목적 제품의 수명주기나 업무 프로세스를 시각적인 플로우로 정의하고 관리하는 시스템을 구축합니다. 각 플로우 단계는 데이터베이스 테이블의 레코드 조건으로 정의되며, 데이터를 플로우 단계 간 이동시키고 이력을 관리할 수 있습니다. ### 1.2 주요 기능 - 플로우 정의 및 시각적 편집 - 플로우 단계별 조건 설정 - 화면관리 시스템과 연동하여 플로우 위젯 배치 - 플로우 단계별 데이터 카운트 및 리스트 조회 - 데이터의 플로우 단계 이동 - 상태 변경 이력 관리 (오딧 로그) ### 1.3 사용 예시 **DTG 제품 수명주기 관리** - 플로우 이름: "DTG 제품 라이프사이클" - 연결 테이블: `product_dtg` - 플로우 단계: 1. 구매 (조건: `status = '구매완료' AND install_date IS NULL`) 2. 설치 (조건: `status = '설치완료' AND disposal_date IS NULL`) 3. 폐기 (조건: `status = '폐기완료'`) --- ## 2. 데이터베이스 스키마 ### 2.1 flow_definition (플로우 정의) ```sql CREATE TABLE flow_definition ( id SERIAL PRIMARY KEY, name VARCHAR(200) NOT NULL, -- 플로우 이름 description TEXT, -- 플로우 설명 table_name VARCHAR(200) NOT NULL, -- 연결된 테이블명 is_active BOOLEAN DEFAULT true, -- 활성화 여부 created_by VARCHAR(100), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_flow_definition_table ON flow_definition(table_name); ``` ### 2.2 flow_step (플로우 단계) ```sql CREATE TABLE flow_step ( id SERIAL PRIMARY KEY, flow_definition_id INTEGER NOT NULL REFERENCES flow_definition(id) ON DELETE CASCADE, step_name VARCHAR(200) NOT NULL, -- 단계 이름 step_order INTEGER NOT NULL, -- 단계 순서 condition_json JSONB, -- 조건 설정 (JSON 형태) color VARCHAR(50) DEFAULT '#3B82F6', -- 단계 표시 색상 position_x INTEGER DEFAULT 0, -- 캔버스 상의 X 좌표 position_y INTEGER DEFAULT 0, -- 캔버스 상의 Y 좌표 created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_flow_step_definition ON flow_step(flow_definition_id); ``` **condition_json 예시:** ```json { "type": "AND", "conditions": [ { "column": "status", "operator": "equals", "value": "구매완료" }, { "column": "install_date", "operator": "is_null", "value": null } ] } ``` ### 2.3 flow_step_connection (플로우 단계 연결) ```sql CREATE TABLE flow_step_connection ( id SERIAL PRIMARY KEY, flow_definition_id INTEGER NOT NULL REFERENCES flow_definition(id) ON DELETE CASCADE, from_step_id INTEGER NOT NULL REFERENCES flow_step(id) ON DELETE CASCADE, to_step_id INTEGER NOT NULL REFERENCES flow_step(id) ON DELETE CASCADE, label VARCHAR(200), -- 연결선 라벨 (선택사항) created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_flow_connection_definition ON flow_step_connection(flow_definition_id); ``` ### 2.4 flow_data_status (데이터의 현재 플로우 상태) ```sql CREATE TABLE flow_data_status ( id SERIAL PRIMARY KEY, flow_definition_id INTEGER NOT NULL REFERENCES flow_definition(id) ON DELETE CASCADE, table_name VARCHAR(200) NOT NULL, -- 원본 테이블명 record_id VARCHAR(100) NOT NULL, -- 원본 테이블의 레코드 ID current_step_id INTEGER REFERENCES flow_step(id) ON DELETE SET NULL, updated_by VARCHAR(100), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(flow_definition_id, table_name, record_id) ); CREATE INDEX idx_flow_data_status_record ON flow_data_status(table_name, record_id); CREATE INDEX idx_flow_data_status_step ON flow_data_status(current_step_id); ``` ### 2.5 flow_audit_log (플로우 상태 변경 이력) ```sql CREATE TABLE flow_audit_log ( id SERIAL PRIMARY KEY, flow_definition_id INTEGER NOT NULL REFERENCES flow_definition(id) ON DELETE CASCADE, table_name VARCHAR(200) NOT NULL, record_id VARCHAR(100) NOT NULL, from_step_id INTEGER REFERENCES flow_step(id) ON DELETE SET NULL, to_step_id INTEGER REFERENCES flow_step(id) ON DELETE SET NULL, changed_by VARCHAR(100), changed_at TIMESTAMP DEFAULT NOW(), note TEXT -- 변경 사유 ); CREATE INDEX idx_flow_audit_record ON flow_audit_log(table_name, record_id, changed_at DESC); CREATE INDEX idx_flow_audit_definition ON flow_audit_log(flow_definition_id); ``` --- ## 3. 백엔드 API 설계 ### 3.1 플로우 정의 관리 API #### 3.1.1 플로우 생성 ``` POST /api/flow/definitions Body: { name: string; description?: string; tableName: string; } Response: { success: boolean; data: FlowDefinition } ``` #### 3.1.2 플로우 목록 조회 ``` GET /api/flow/definitions Query: { tableName?: string; isActive?: boolean } Response: { success: boolean; data: FlowDefinition[] } ``` #### 3.1.3 플로우 상세 조회 ``` GET /api/flow/definitions/:id Response: { success: boolean; data: { definition: FlowDefinition; steps: FlowStep[]; connections: FlowStepConnection[]; } } ``` #### 3.1.4 플로우 수정 ``` PUT /api/flow/definitions/:id Body: { name?: string; description?: string; isActive?: boolean } Response: { success: boolean; data: FlowDefinition } ``` #### 3.1.5 플로우 삭제 ``` DELETE /api/flow/definitions/:id Response: { success: boolean } ``` ### 3.2 플로우 단계 관리 API #### 3.2.1 단계 추가 ``` POST /api/flow/definitions/:flowId/steps Body: { stepName: string; stepOrder: number; conditionJson?: object; color?: string; positionX?: number; positionY?: number; } Response: { success: boolean; data: FlowStep } ``` #### 3.2.2 단계 수정 ``` PUT /api/flow/steps/:stepId Body: { stepName?: string; stepOrder?: number; conditionJson?: object; color?: string; positionX?: number; positionY?: number; } Response: { success: boolean; data: FlowStep } ``` #### 3.2.3 단계 삭제 ``` DELETE /api/flow/steps/:stepId Response: { success: boolean } ``` ### 3.3 플로우 연결 관리 API #### 3.3.1 단계 연결 생성 ``` POST /api/flow/connections Body: { flowDefinitionId: number; fromStepId: number; toStepId: number; label?: string; } Response: { success: boolean; data: FlowStepConnection } ``` #### 3.3.2 연결 삭제 ``` DELETE /api/flow/connections/:connectionId Response: { success: boolean } ``` ### 3.4 플로우 실행 API #### 3.4.1 단계별 데이터 카운트 조회 ``` GET /api/flow/:flowId/step/:stepId/count Response: { success: boolean; data: { count: number } } ``` #### 3.4.2 단계별 데이터 리스트 조회 ``` GET /api/flow/:flowId/step/:stepId/data Query: { page?: number; pageSize?: number } Response: { success: boolean; data: { records: any[]; total: number; page: number; pageSize: number; } } ``` #### 3.4.3 데이터를 다음 단계로 이동 ``` POST /api/flow/move Body: { flowId: number; recordId: string; toStepId: number; note?: string; } Response: { success: boolean } ``` #### 3.4.4 데이터의 플로우 이력 조회 ``` GET /api/flow/audit/:flowId/:recordId Response: { success: boolean; data: FlowAuditLog[] } ``` --- ## 4. 프론트엔드 구조 ### 4.1 플로우 관리 화면 (`/flow-management`) #### 4.1.1 파일 구조 ``` frontend/src/app/flow-management/ ├── page.tsx # 메인 페이지 ├── components/ │ ├── FlowList.tsx # 플로우 목록 │ ├── FlowEditor.tsx # 플로우 편집기 (React Flow) │ ├── FlowStepPanel.tsx # 단계 속성 편집 패널 │ ├── FlowConditionBuilder.tsx # 조건 설정 빌더 │ └── FlowPreview.tsx # 플로우 미리보기 ``` #### 4.1.2 주요 컴포넌트 **FlowEditor.tsx** - React Flow 라이브러리 사용 - 플로우 단계를 노드로 표시 - 단계 간 연결선 표시 - 드래그앤드롭으로 노드 위치 조정 - 노드 클릭 시 FlowStepPanel 표시 **FlowConditionBuilder.tsx** - 테이블 컬럼 선택 - 연산자 선택 (equals, not_equals, in, not_in, greater_than, less_than, is_null, is_not_null) - 값 입력 - AND/OR 조건 그룹핑 - 조건 추가/제거 ### 4.2 화면관리 연동 #### 4.2.1 새로운 컴포넌트 타입 추가 **types/screen.ts에 추가:** ```typescript export interface FlowWidgetComponent extends BaseComponent { type: "flow-widget"; flowId?: number; layout?: "horizontal" | "vertical"; cardWidth?: string; cardHeight?: string; showCount?: boolean; showConnections?: boolean; } export type ComponentData = | ContainerComponent | WidgetComponent | GroupComponent | DataTableComponent | ButtonComponent | SplitPanelComponent | RepeaterComponent | FlowWidgetComponent; // 추가 ``` #### 4.2.2 FlowWidgetConfigPanel.tsx 생성 ```typescript // 플로우 위젯 설정 패널 interface FlowWidgetConfigPanelProps { component: FlowWidgetComponent; onUpdateProperty: (property: string, value: any) => void; } export function FlowWidgetConfigPanel({ component, onUpdateProperty, }: FlowWidgetConfigPanelProps) { const [flows, setFlows] = useState([]); useEffect(() => { // 플로우 목록 불러오기 loadFlows(); }, []); return (
onUpdateProperty("cardWidth", e.target.value)} />
onUpdateProperty("showCount", checked)} />
onUpdateProperty("showConnections", checked) } />
); } ``` #### 4.2.3 RealtimePreview.tsx 수정 ```typescript // flow-widget 타입 렌더링 추가 if (component.type === "flow-widget" && component.flowId) { return ; } ``` #### 4.2.4 FlowWidgetPreview.tsx 생성 ```typescript interface FlowWidgetPreviewProps { component: FlowWidgetComponent; interactive?: boolean; // InteractiveScreenViewer에서 true } export function FlowWidgetPreview({ component, interactive, }: FlowWidgetPreviewProps) { const [flowData, setFlowData] = useState(null); const [stepCounts, setStepCounts] = useState>({}); useEffect(() => { if (component.flowId) { loadFlowData(component.flowId); } }, [component.flowId]); const loadFlowData = async (flowId: number) => { const response = await fetch(`/api/flow/definitions/${flowId}`); const result = await response.json(); if (result.success) { setFlowData(result.data); // 각 단계별 데이터 카운트 조회 loadStepCounts(result.data.steps); } }; const loadStepCounts = async (steps: FlowStep[]) => { const counts: Record = {}; for (const step of steps) { const response = await fetch( `/api/flow/${component.flowId}/step/${step.id}/count` ); const result = await response.json(); if (result.success) { counts[step.id] = result.data.count; } } setStepCounts(counts); }; const handleStepClick = async (stepId: number) => { if (!interactive) return; // 단계 클릭 시 데이터 리스트 모달 표시 // TODO: 구현 }; const layout = component.layout || "horizontal"; const cardWidth = component.cardWidth || "200px"; const cardHeight = component.cardHeight || "120px"; return (
{flowData?.steps.map((step: FlowStep, index: number) => ( handleStepClick(step.id)} > {step.stepName} {component.showCount && (
{stepCounts[step.id] || 0}
)}
{component.showConnections && index < flowData.steps.length - 1 && (
{layout === "vertical" ? "↓" : "→"}
)}
))}
); } ``` ### 4.3 데이터 리스트 모달 #### 4.3.1 FlowStepDataModal.tsx 생성 ```typescript interface FlowStepDataModalProps { flowId: number; stepId: number; stepName: string; isOpen: boolean; onClose: () => void; } export function FlowStepDataModal({ flowId, stepId, stepName, isOpen, onClose, }: FlowStepDataModalProps) { const [data, setData] = useState([]); const [selectedRows, setSelectedRows] = useState>(new Set()); useEffect(() => { if (isOpen) { loadStepData(); } }, [isOpen, stepId]); const loadStepData = async () => { const response = await fetch(`/api/flow/${flowId}/step/${stepId}/data`); const result = await response.json(); if (result.success) { setData(result.data.records); } }; const handleMoveToNextStep = async () => { // 선택된 레코드들을 다음 단계로 이동 // TODO: 구현 }; return ( {stepName} - 데이터 목록
{/* 데이터 테이블 */}
); } ``` ### 4.4 오딧 로그 화면 #### 4.4.1 FlowAuditLog.tsx 생성 ```typescript interface FlowAuditLogProps { flowId: number; recordId: string; } export function FlowAuditLog({ flowId, recordId }: FlowAuditLogProps) { const [logs, setLogs] = useState([]); useEffect(() => { loadAuditLogs(); }, [flowId, recordId]); const loadAuditLogs = async () => { const response = await fetch(`/api/flow/audit/${flowId}/${recordId}`); const result = await response.json(); if (result.success) { setLogs(result.data); } }; return (

상태 변경 이력

{logs.map((log) => (
{log.fromStepName || "시작"} {log.toStepName}
변경자: {log.changedBy}
변경일시: {new Date(log.changedAt).toLocaleString()}
{log.note &&
변경사유: {log.note}
}
))}
); } ``` --- ## 5. 백엔드 구현 ### 5.1 서비스 파일 구조 ``` backend-node/src/services/ ├── flowDefinitionService.ts # 플로우 정의 관리 ├── flowStepService.ts # 플로우 단계 관리 ├── flowConnectionService.ts # 플로우 연결 관리 ├── flowExecutionService.ts # 플로우 실행 (카운트, 데이터 조회) ├── flowDataMoveService.ts # 데이터 이동 및 오딧 로그 └── flowConditionParser.ts # 조건 JSON을 SQL WHERE절로 변환 ``` ### 5.2 flowConditionParser.ts 핵심 로직 ```typescript export interface FlowCondition { column: string; operator: | "equals" | "not_equals" | "in" | "not_in" | "greater_than" | "less_than" | "is_null" | "is_not_null"; value: any; } export interface FlowConditionGroup { type: "AND" | "OR"; conditions: FlowCondition[]; } export class FlowConditionParser { /** * 조건 JSON을 SQL WHERE 절로 변환 */ static toSqlWhere(conditionGroup: FlowConditionGroup): { where: string; params: any[]; } { const conditions: string[] = []; const params: any[] = []; let paramIndex = 1; for (const condition of conditionGroup.conditions) { const column = this.sanitizeColumnName(condition.column); switch (condition.operator) { case "equals": conditions.push(`${column} = $${paramIndex}`); params.push(condition.value); paramIndex++; break; case "not_equals": conditions.push(`${column} != $${paramIndex}`); params.push(condition.value); paramIndex++; break; case "in": if (Array.isArray(condition.value) && condition.value.length > 0) { const placeholders = condition.value .map(() => `$${paramIndex++}`) .join(", "); conditions.push(`${column} IN (${placeholders})`); params.push(...condition.value); } break; case "not_in": if (Array.isArray(condition.value) && condition.value.length > 0) { const placeholders = condition.value .map(() => `$${paramIndex++}`) .join(", "); conditions.push(`${column} NOT IN (${placeholders})`); params.push(...condition.value); } break; case "greater_than": conditions.push(`${column} > $${paramIndex}`); params.push(condition.value); paramIndex++; break; case "less_than": conditions.push(`${column} < $${paramIndex}`); params.push(condition.value); paramIndex++; break; case "is_null": conditions.push(`${column} IS NULL`); break; case "is_not_null": conditions.push(`${column} IS NOT NULL`); break; } } const joinOperator = conditionGroup.type === "OR" ? " OR " : " AND "; const where = conditions.length > 0 ? conditions.join(joinOperator) : "1=1"; return { where, params }; } /** * SQL 인젝션 방지를 위한 컬럼명 검증 */ private static sanitizeColumnName(columnName: string): string { // 알파벳, 숫자, 언더스코어만 허용 if (!/^[a-zA-Z0-9_]+$/.test(columnName)) { throw new Error(`Invalid column name: ${columnName}`); } return columnName; } } ``` ### 5.3 flowExecutionService.ts 핵심 로직 ```typescript export class FlowExecutionService { /** * 특정 플로우 단계에 해당하는 데이터 카운트 */ async getStepDataCount(flowId: number, stepId: number): Promise { // 1. 플로우 정의 조회 const flowDef = await this.getFlowDefinition(flowId); // 2. 플로우 단계 조회 const step = await this.getFlowStep(stepId); // 3. 조건 JSON을 SQL WHERE절로 변환 const { where, params } = FlowConditionParser.toSqlWhere( step.conditionJson ); // 4. 카운트 쿼리 실행 const query = `SELECT COUNT(*) as count FROM ${flowDef.tableName} WHERE ${where}`; const result = await db.query(query, params); return parseInt(result.rows[0].count); } /** * 특정 플로우 단계에 해당하는 데이터 리스트 */ async getStepDataList( flowId: number, stepId: number, page: number = 1, pageSize: number = 20 ): Promise<{ records: any[]; total: number }> { const flowDef = await this.getFlowDefinition(flowId); const step = await this.getFlowStep(stepId); const { where, params } = FlowConditionParser.toSqlWhere( step.conditionJson ); const offset = (page - 1) * pageSize; // 전체 카운트 const countQuery = `SELECT COUNT(*) as count FROM ${flowDef.tableName} WHERE ${where}`; const countResult = await db.query(countQuery, params); const total = parseInt(countResult.rows[0].count); // 데이터 조회 const dataQuery = ` SELECT * FROM ${flowDef.tableName} WHERE ${where} ORDER BY id DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2} `; const dataResult = await db.query(dataQuery, [...params, pageSize, offset]); return { records: dataResult.rows, total, }; } } ``` ### 5.4 flowDataMoveService.ts 핵심 로직 ```typescript export class FlowDataMoveService { /** * 데이터를 다음 플로우 단계로 이동 */ async moveDataToStep( flowId: number, recordId: string, toStepId: number, userId: string, note?: string ): Promise { const client = await db.getClient(); try { await client.query("BEGIN"); // 1. 현재 상태 조회 const currentStatus = await this.getCurrentStatus( client, flowId, recordId ); const fromStepId = currentStatus?.currentStepId || null; // 2. flow_data_status 업데이트 또는 삽입 if (currentStatus) { await client.query( ` UPDATE flow_data_status SET current_step_id = $1, updated_by = $2, updated_at = NOW() WHERE flow_definition_id = $3 AND record_id = $4 `, [toStepId, userId, flowId, recordId] ); } else { const flowDef = await this.getFlowDefinition(flowId); await client.query( ` INSERT INTO flow_data_status (flow_definition_id, table_name, record_id, current_step_id, updated_by) VALUES ($1, $2, $3, $4, $5) `, [flowId, flowDef.tableName, recordId, toStepId, userId] ); } // 3. 오딧 로그 기록 await client.query( ` INSERT INTO flow_audit_log (flow_definition_id, table_name, record_id, from_step_id, to_step_id, changed_by, note) VALUES ($1, $2, $3, $4, $5, $6, $7) `, [ flowId, currentStatus?.tableName || recordId, recordId, fromStepId, toStepId, userId, note, ] ); await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); throw error; } finally { client.release(); } } /** * 데이터의 플로우 이력 조회 */ async getAuditLogs(flowId: number, recordId: string): Promise { const query = ` SELECT fal.*, fs_from.step_name as from_step_name, fs_to.step_name as to_step_name FROM flow_audit_log fal LEFT JOIN flow_step fs_from ON fal.from_step_id = fs_from.id LEFT JOIN flow_step fs_to ON fal.to_step_id = fs_to.id WHERE fal.flow_definition_id = $1 AND fal.record_id = $2 ORDER BY fal.changed_at DESC `; const result = await db.query(query, [flowId, recordId]); return result.rows; } } ``` --- ## 6. 구현 단계 ### Phase 1: 기본 구조 (1주) - [ ] 데이터베이스 마이그레이션 생성 및 실행 - [ ] 백엔드 서비스 기본 구조 생성 - [ ] 플로우 정의 CRUD API 구현 - [ ] 플로우 단계 CRUD API 구현 - [ ] 플로우 연결 API 구현 ### Phase 2: 플로우 편집기 (1주) - [ ] React Flow 라이브러리 설치 및 설정 - [ ] FlowEditor 컴포넌트 구현 - [ ] FlowList 컴포넌트 구현 - [ ] FlowStepPanel 구현 (단계 속성 편집) - [ ] FlowConditionBuilder 구현 (조건 설정 UI) - [ ] 플로우 저장/불러오기 기능 ### Phase 3: 화면관리 연동 (3일) - [ ] FlowWidgetComponent 타입 정의 - [ ] FlowWidgetConfigPanel 구현 - [ ] FlowWidgetPreview 구현 - [ ] RealtimePreview에 flow-widget 렌더링 추가 - [ ] InteractiveScreenViewer에 flow-widget 렌더링 추가 ### Phase 4: 플로우 실행 기능 (1주) - [ ] FlowConditionParser 구현 (조건 → SQL 변환) - [ ] FlowExecutionService 구현 (카운트, 데이터 조회) - [ ] 단계별 데이터 카운트 API - [ ] 단계별 데이터 리스트 API - [ ] FlowStepDataModal 구현 - [ ] 데이터 선택 및 표시 기능 ### Phase 5: 데이터 이동 및 오딧 (3일) - [ ] FlowDataMoveService 구현 - [ ] 데이터 이동 API - [ ] 오딧 로그 API - [ ] FlowAuditLog 컴포넌트 구현 - [ ] 데이터 이동 시 트랜잭션 처리 ### Phase 6: 테스트 및 최적화 (3일) - [ ] 단위 테스트 작성 - [ ] 통합 테스트 작성 - [ ] 성능 최적화 (인덱스, 쿼리 최적화) - [ ] 사용자 테스트 및 피드백 반영 --- ## 7. 기술 스택 ### 7.1 프론트엔드 - **React Flow**: 플로우 시각화 및 편집 - **Shadcn/ui**: UI 컴포넌트 - **TanStack Query**: 데이터 페칭 및 캐싱 ### 7.2 백엔드 - **Node.js + Express**: API 서버 - **PostgreSQL**: 데이터베이스 - **TypeScript**: 타입 안전성 --- ## 8. 주요 고려사항 ### 8.1 성능 - 플로우 단계별 데이터 카운트 조회 시 인덱스 활용 - 대용량 데이터 처리 시 페이징 필수 - 조건 파싱 결과 캐싱 ### 8.2 보안 - SQL 인젝션 방지: 파라미터 바인딩 사용 - 컬럼명 검증: 알파벳, 숫자, 언더스코어만 허용 - 사용자 권한 확인 ### 8.3 확장성 - 복잡한 조건 (중첩 AND/OR) 지원 가능하도록 설계 - 플로우 분기 (조건부 분기) 지원 가능하도록 설계 - 자동 플로우 이동 (트리거) 추후 추가 가능 ### 8.4 사용성 - 플로우 편집기의 직관적인 UI - 드래그앤드롭으로 쉬운 조작 - 실시간 데이터 카운트 표시 - 오딧 로그를 통한 추적 가능성 --- ## 9. 예상 일정 | Phase | 기간 | 담당 | | ---------------------------- | ---------- | ------------------ | | Phase 1: 기본 구조 | 1주 | Backend | | Phase 2: 플로우 편집기 | 1주 | Frontend | | Phase 3: 화면관리 연동 | 3일 | Frontend | | Phase 4: 플로우 실행 | 1주 | Backend + Frontend | | Phase 5: 데이터 이동 및 오딧 | 3일 | Backend + Frontend | | Phase 6: 테스트 및 최적화 | 3일 | 전체 | | **총 예상 기간** | **약 4주** | | --- ## 10. 향후 확장 계획 ### 10.1 자동 플로우 이동 - 특정 조건 충족 시 자동으로 다음 단계로 이동 - 예: 결재 승인 시 자동으로 '승인완료' 단계로 이동 ### 10.2 플로우 분기 - 조건에 따라 다른 경로로 분기 - 예: 금액에 따라 '일반 승인' 또는 '특별 승인' 경로 ### 10.3 플로우 알림 - 특정 단계 진입 시 담당자에게 알림 - 이메일, 시스템 알림 등 ### 10.4 플로우 템플릿 - 자주 사용하는 플로우 패턴을 템플릿으로 저장 - 템플릿에서 새 플로우 생성 ### 10.5 플로우 통계 대시보드 - 단계별 체류 시간 분석 - 병목 구간 식별 - 처리 속도 통계 --- ## 11. 참고 자료 ### 11.1 React Flow - 공식 문서: https://reactflow.dev/ - 예제: https://reactflow.dev/examples ### 11.2 유사 시스템 - Jira Workflow - Trello Board - GitHub Projects --- ## 12. 결론 이 플로우 관리 시스템은 제품 수명주기, 업무 프로세스, 승인 프로세스 등 다양한 비즈니스 워크플로우를 시각적으로 정의하고 관리할 수 있는 강력한 도구입니다. 화면관리 시스템과의 긴밀한 통합을 통해 사용자는 코드 없이도 복잡한 워크플로우를 구축하고 실행할 수 있으며, 오딧 로그를 통해 모든 상태 변경을 추적할 수 있습니다. 단계별 구현을 통해 안정적으로 개발하고, 향후 확장 가능성을 염두에 두어 장기적으로 유지보수하기 쉬운 시스템을 구축할 수 있습니다.