diff --git a/docs/DB_WORKFLOW_ANALYSIS.md b/docs/DB_WORKFLOW_ANALYSIS.md new file mode 100644 index 00000000..d5cd069b --- /dev/null +++ b/docs/DB_WORKFLOW_ANALYSIS.md @@ -0,0 +1,1728 @@ +# WACE ERP 데이터베이스 워크플로우 분석 + +> 📅 작성일: 2026-02-06 +> 🎯 목적: 비즈니스 워크플로우 중심의 DB 구조 분석 +> 📊 DB 엔진: PostgreSQL 16.8 +> 📝 기반 스키마: plm_schema_20260120.sql + +--- + +## 📋 Executive Summary + +WACE ERP 시스템은 **멀티테넌트 SaaS 아키텍처**를 기반으로 한 제조업 특화 ERP입니다. +- **총 테이블 수**: 337개 +- **핵심 아키텍처**: Multi-tenancy (company_code 기반 데이터 격리) +- **특징**: 메타데이터 드리븐, 동적 스키마, 플로우 기반 통합 + +--- + +## 🏗️ 1. 데이터베이스 아키텍처 개요 + +### 1.1 계층 구조 + +``` +┌─────────────────────────────────────────────────────────┐ +│ Application Layer (React) │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ Backend Layer (Node.js + TypeScript) │ +│ - API Routes │ +│ - Business Logic Services │ +│ - Raw SQL Query Execution │ +└─────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────┐ +│ Database Layer (PostgreSQL) │ +│ │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ System Core │ │ Metadata │ │ +│ │ - user_info │ │ - table_labels │ │ +│ │ - company_mng │ │ - screen_def │ │ +│ │ - menu_info │ │ - flow_def │ │ +│ └──────────────────┘ └──────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Business Domain Tables │ │ +│ │ - Sales (30+) - Purchase (25+) │ │ +│ │ - Stock (20+) - Production (25+) │ │ +│ │ - Quality (15+) - Logistics (20+) │ │ +│ │ - PLM (30+) - Accounting (20+) │ │ +│ └─────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 1.2 멀티테넌시 핵심 원칙 + +**ABSOLUTE MUST: 모든 테이블에 company_code** + +```sql +-- ✅ 표준 테이블 구조 +CREATE TABLE {table_name} ( + id VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + company_code VARCHAR(20) NOT NULL, -- 필수! + created_date TIMESTAMP DEFAULT NOW(), + updated_date TIMESTAMP DEFAULT NOW(), + writer VARCHAR(500), + -- ... 비즈니스 컬럼들 +); + +-- 필수 인덱스 +CREATE INDEX idx_{table_name}_company_code +ON {table_name}(company_code); +``` + +**company_code = "*" 의미** +- ❌ 잘못된 이해: 모든 회사 공통 데이터 +- ✅ 올바른 이해: 슈퍼 관리자 전용 데이터 +- 일반 회사 쿼리: `WHERE company_code = $1 AND company_code != '*'` + +--- + +## 🎯 2. 핵심 시스템 테이블 (System Core) + +### 2.1 사용자 및 인증 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `user_info` | 사용자 정보 | user_id, user_name, email, password_hash, company_code | +| `user_info_history` | 사용자 변경 이력 | history_id, user_id, change_type, changed_at | +| `auth_tokens` | 인증 토큰 | token_id, user_id, token, expires_at | +| `login_access_log` | 로그인 이력 | log_id, user_id, ip_address, login_at | +| `user_dept` | 사용자-부서 매핑 (겸직 지원) | user_id, dept_code, is_primary | +| `user_dept_sub` | 겸직 부서 정보 | user_id, sub_dept_code | + +**워크플로우:** +``` +로그인 → auth_tokens 생성 → login_access_log 기록 +회사별 데이터 격리 → company_code 필터링 +``` + +### 2.2 권한 관리 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `authority_master` | 권한 그룹 마스터 | objid, auth_name, auth_code, company_code | +| `authority_master_history` | 권한 그룹 이력 | objid, parent_objid, history_type | +| `authority_sub_user` | 권한 그룹 멤버 | objid, master_objid, user_id | +| `rel_menu_auth` | 메뉴별 권한 CRUD | objid, menu_objid, auth_objid, create_auth, read_auth, update_auth, delete_auth | + +**권한 체계:** +``` +사용자 → authority_sub_user → authority_master → rel_menu_auth → 메뉴별 CRUD 권한 +``` + +### 2.3 회사 및 부서 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `company_mng` | 회사 정보 | company_code (PK), company_name, business_registration_number | +| `company_code_sequence` | 회사 코드 시퀀스 | company_code, next_sequence | +| `dept_info` | 부서 정보 | dept_code, dept_name, parent_dept_code, company_code | +| `dept_info_history` | 부서 변경 이력 | dept_code, change_type, changed_at | + +**계층 구조:** +``` +company_mng (회사) + └─ dept_info (부서 - 계층 구조) + └─ user_dept (사용자 배정) +``` + +### 2.4 메뉴 시스템 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `menu_info` | 메뉴 정보 | objid, menu_type, parent_obj_id, menu_name_kor, menu_url, screen_code, company_code, source_menu_objid | +| `menu_screen_groups` | 통합 메뉴/화면 그룹 | group_id, group_name, parent_group_id, company_code | +| `menu_screen_group_items` | 그룹-화면 연결 | group_id, screen_code | +| `screen_menu_assignments` | 화면-메뉴 할당 | screen_code, menu_id | + +**메뉴 타입:** +- `menu_type = 0`: 일반 메뉴 +- `menu_type = 1`: 시스템 관리 메뉴 +- `menu_type = 2`: 동적 생성 메뉴 (screen_definitions에서 자동 생성) + +**메뉴 복사 메커니즘:** +``` +원본 메뉴 (회사A) → 복사 → 새 메뉴 (회사B) +source_menu_objid: 원본 메뉴의 objid 추적 +재복사 시: source_menu_objid로 기존 복사본 찾아서 덮어쓰기 +``` + +--- + +## 🗂️ 3. 메타데이터 시스템 (Metadata Layer) + +### 3.1 테이블 메타데이터 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `table_labels` | 테이블 논리명 | table_name (PK), table_label, description | +| `table_type_columns` | 컬럼 타입 정의 (회사별) | table_name, column_name, company_code, input_type, detail_settings, display_order | +| `column_labels` | 컬럼 논리명 (레거시) | table_name, column_name, column_label, input_type | +| `table_relationships` | 테이블 관계 정의 | parent_table, child_table, join_condition | +| `table_log_config` | 테이블 로그 설정 | table_name, log_enabled, log_table_name | + +**메타데이터 구조:** +``` +table_labels (테이블 논리명) + └─ table_type_columns (컬럼 타입 정의 - 회사별) + └─ category_column_mapping (카테고리 컬럼 매핑) + └─ table_column_category_values (카테고리 값) +``` + +**동적 테이블 생성 프로세스:** +1. `CREATE TABLE` 실행 +2. `table_labels` 등록 +3. `table_type_columns` 등록 (회사별) +4. `column_labels` 등록 (레거시 호환) +5. `ddl_execution_log`에 DDL 실행 이력 기록 + +### 3.2 화면 메타데이터 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `screen_definitions` | 화면 정의 | screen_code (PK), screen_name, table_name, screen_type, company_code | +| `screen_layouts` | 화면 레이아웃 | screen_code, layout_config (JSONB) | +| `screen_templates` | 화면 템플릿 | template_id, template_name, template_config | +| `screen_widgets` | 화면 위젯 | widget_id, screen_code, widget_type, widget_config | +| `screen_groups` | 화면 그룹 | group_id, group_name, parent_group_id | +| `screen_group_screens` | 화면-그룹 연결 | group_id, screen_code | + +**화면 생성 워크플로우:** +``` +screen_definitions 생성 + → 트리거 발동 → menu_info 자동 생성 (menu_type=2) + → screen_layouts 레이아웃 정의 + → screen_widgets 위젯 배치 + → screen_table_relations 테이블 관계 설정 +``` + +### 3.3 화면 고급 기능 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `screen_embedding` | 화면 임베딩 | parent_screen, child_screen, embed_config | +| `screen_split_panel` | 분할 패널 | screen_code, left_screen, right_screen, split_ratio | +| `screen_data_transfer` | 화면 간 데이터 전달 | source_screen, target_screen, mapping_config | +| `screen_data_flows` | 화면 간 데이터 흐름 | flow_id, source_screen, target_screen, flow_type | +| `screen_field_joins` | 화면 필드 조인 | source_table, target_table, join_condition | +| `screen_table_relations` | 화면-테이블 관계 | screen_code, table_name, relation_type | + +**화면 임베딩 패턴:** +``` +마스터 화면 (sales_order) + ├─ 좌측 패널: 수주 목록 + └─ 우측 패널: 상세 정보 (임베딩) + ├─ 수주 기본 정보 + ├─ 수주 품목 (임베딩) + └─ 배송 정보 (임베딩) +``` + +### 3.4 UI 컴포넌트 표준 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `component_standards` | UI 컴포넌트 표준 | component_type, standard_props (JSONB) | +| `button_action_standards` | 버튼 액션 기준 | action_type, action_config (JSONB) | +| `web_type_standards` | 웹 타입 기준 | web_type, type_config (JSONB) | +| `grid_standards` | 격자 시스템 기준 | grid_type, grid_config (JSONB) | +| `layout_standards` | 레이아웃 표준 | layout_type, layout_config (JSONB) | +| `layout_instances` | 레이아웃 인스턴스 | instance_id, layout_type, instance_config | +| `style_templates` | 스타일 템플릿 | template_id, template_name, style_config (JSONB) | + +--- + +## 🔄 4. 플로우 및 데이터 통합 시스템 + +### 4.1 플로우 정의 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `flow_definition` | 플로우 정의 | id, name, table_name, db_source_type, company_code | +| `flow_step` | 플로우 단계 | step_id, flow_id, step_name, step_type, step_order, action_config (JSONB) | +| `flow_step_connection` | 플로우 단계 연결 | connection_id, source_step_id, target_step_id, condition | +| `flow_data_mapping` | 플로우 데이터 매핑 | mapping_id, flow_id, source_table, target_table, mapping_config | + +**플로우 타입:** +- 승인 플로우: 결재 라인 정의 +- 상태 플로우: 데이터 상태 전환 +- 데이터 플로우: 테이블 간 데이터 이동 +- 통합 플로우: 외부 시스템 연동 + +### 4.2 플로우 실행 및 모니터링 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `flow_data_status` | 데이터 현재 상태 | data_id, flow_id, current_step_id, status | +| `flow_audit_log` | 플로우 상태 변경 이력 | log_id, flow_id, data_id, from_step, to_step, changed_at, changed_by | +| `flow_integration_log` | 플로우 외부 연동 로그 | log_id, flow_id, integration_type, request_data, response_data | + +**플로우 실행 예시: 수주 승인** +``` +수주 등록 (sales_order_mng) + → flow_data_status 생성 (status: 'PENDING') + → flow_step 1: 영업팀장 승인 + → flow_step 2: 재고 확인 + → flow_step 3: 생산계획 생성 + → flow_step 4: 최종 승인 + → flow_audit_log 각 단계 기록 +``` + +### 4.3 노드 기반 플로우 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `node_flows` | 노드 기반 플로우 | flow_id, flow_name, nodes (JSONB), edges (JSONB) | +| `dataflow_diagrams` | 데이터플로우 다이어그램 | diagram_id, diagram_name, diagram_data (JSONB) | +| `dataflow_external_calls` | 데이터플로우 외부 호출 | call_id, diagram_id, external_connection_id | + +**노드 타입:** +- Start/End Node +- Data Node (테이블 조회/저장) +- Logic Node (조건 분기, 계산) +- External Node (외부 API 호출) +- Transform Node (데이터 변환) + +--- + +## 🌐 5. 외부 연동 시스템 + +### 5.1 외부 데이터베이스 연결 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `external_db_connections` | 외부 DB 연결 정보 | connection_id, connection_name, db_type, host, port, database, username, password_encrypted | +| `external_db_connection` | 외부 DB 연결 (레거시) | connection_id, connection_config (JSONB) | +| `external_connection_permission` | 외부 연결 권한 | permission_id, connection_id, user_id, allowed_operations | +| `flow_external_db_connection` | 플로우 전용 외부 DB 연결 | connection_id, flow_id, db_config (JSONB) | +| `flow_external_connection_permission` | 플로우 외부 연결 권한 | permission_id, flow_id, connection_id | + +**지원 DB 타입:** +- PostgreSQL +- MySQL +- MS SQL Server +- Oracle +- MongoDB (NoSQL) + +### 5.2 외부 REST API 연결 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `external_rest_api_connections` | 외부 REST API 연결 | connection_id, api_name, base_url, auth_type, auth_config (JSONB) | +| `external_call_configs` | 외부 호출 설정 | config_id, connection_id, endpoint, method, headers (JSONB) | +| `external_call_logs` | 외부 호출 로그 | log_id, config_id, request_data, response_data, status_code, executed_at | + +**인증 방식:** +- API Key +- Bearer Token +- OAuth 2.0 +- Basic Auth + +### 5.3 데이터 수집 배치 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `data_collection_configs` | 데이터 수집 설정 | config_id, source_type, source_config (JSONB), schedule | +| `data_collection_jobs` | 데이터 수집 작업 | job_id, config_id, job_status, started_at, completed_at | +| `data_collection_history` | 데이터 수집 이력 | history_id, job_id, collected_count, error_count | +| `collection_batch_management` | 수집 배치 관리 | batch_id, batch_name, batch_config (JSONB) | +| `collection_batch_executions` | 배치 실행 이력 | execution_id, batch_id, execution_status, executed_at | + +--- + +## 📊 6. 비즈니스 도메인 테이블 (Business Domain) + +### 6.1 영업/수주 (Sales & Orders) - 30+ 테이블 + +#### 핵심 테이블 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `sales_order_mng` | 수주 관리 | customer_mng, product_mng | +| `sales_order_detail` | 수주 상세 | sales_order_mng | +| `sales_order_detail_log` | 수주 상세 이력 | sales_order_detail | +| `sales_request_master` | 구매요청서 마스터 | customer_mng | +| `sales_request_part` | 구매요청서 품목 | sales_request_master, part_mng | +| `estimate_mgmt` | 견적 관리 | customer_mng, product_mng | +| `contract_mgmt` | 계약 관리 | customer_mng | +| `contract_mgmt_option` | 계약 옵션 | contract_mgmt | + +#### 영업 지원 테이블 + +| 테이블 | 역할 | +|--------|------| +| `sales_bom_report` | 영업 BOM 보고서 | +| `sales_bom_part_qty` | 영업 BOM 수량 | +| `sales_bom_report_part` | 영업 BOM 품목 | +| `sales_long_delivery` | 장납기 부품 리스트 | +| `sales_long_delivery_input` | 장납기 자재 투입 이력 | +| `sales_long_delivery_predict` | 장납기 예측 | +| `sales_part_chg` | 설계 변경 리스트 | +| `sample_supply` | 샘플 공급 | + +#### 고객 관리 + +| 테이블 | 역할 | +|--------|------| +| `customer_mng` | 거래처 마스터 | +| `customer_item` | 거래처별 품번 관리 | +| `customer_item_alias` | 거래처별 품목 품번/품명 | +| `customer_item_mapping` | 거래처별 품목 매핑 | +| `customer_item_price` | 거래처별 품목 단가 이력 | +| `customer_service_mgmt` | 조치내역서 마스터 | +| `customer_service_part` | 조치내역서 사용 부품 | +| `customer_service_workingtime` | 조치내역서 작업 시간 | +| `counselingmgmt` | 상담 관리 | + +**워크플로우: 수주 프로세스** +``` +1. 견적 요청 (estimate_mgmt) +2. 견적 작성 및 발송 +3. 수주 등록 (sales_order_mng) +4. 수주 상세 입력 (sales_order_detail) +5. 계약 체결 (contract_mgmt) +6. 생산 계획 생성 (order_plan_mgmt) +7. 구매 요청 (sales_request_master) +``` + +### 6.2 구매/발주 (Purchase & Procurement) - 25+ 테이블 + +#### 핵심 테이블 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `purchase_order_master` | 발주 관리 마스터 | supplier_mng | +| `purchase_order_part` | 발주서 상세 목록 | purchase_order_master, part_mng | +| `purchase_order_multi` | 동시적용 프로젝트 발주 | purchase_order_master | +| `purchase_order_mng` | 발주 관리 | supplier_mng | +| `purchase_detail` | 구매 상세 | purchase_order | +| `purchase_order` | 구매 주문 | supplier_mng | + +#### 공급처 관리 + +| 테이블 | 역할 | +|--------|------| +| `supplier_mng` | 공급처 마스터 | +| `supplier_mng_log` | 공급처 변경 이력 | +| `supplier_item` | 공급처별 품목 정보 | +| `supplier_item_alias` | 공급처별 품목 품번/품명 | +| `supplier_item_mapping` | 공급처별 품목 매핑 | +| `supplier_item_price` | 공급처별 품목 단가 | +| `procurement_standard` | 구매 기준 정보 | + +#### 입고 관리 + +| 테이블 | 역할 | +|--------|------| +| `delivery_history` | 입고 관리 | +| `delivery_history_defect` | 입고 불량 품목 | +| `delivery_part_price` | 입고 품목 단가 | +| `receiving` | 입고 처리 | +| `receive_history` | 입고 이력 | +| `inbound_mng` | 입고 관리 | +| `check_report_mng` | 검수 관리 보고서 | + +**워크플로우: 구매 프로세스** +``` +1. 구매 요청 (sales_request_master) +2. 공급처 선정 (supplier_mng) +3. 발주서 작성 (purchase_order_master) +4. 발주서 상세 (purchase_order_part) +5. 입고 예정 (delivery_history) +6. 검수 (check_report_mng) +7. 입고 확정 (receiving) +8. 재고 반영 (inventory_stock) +``` + +### 6.3 재고/창고 (Inventory & Warehouse) - 20+ 테이블 + +#### 핵심 테이블 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `inventory_stock` | 재고 현황 | item_info, warehouse_location | +| `inventory_history` | 재고 이력 | inventory_stock | +| `warehouse_info` | 창고 정보 | - | +| `warehouse_location` | 창고 위치 | warehouse_info | +| `inbound_mng` | 입고 관리 | warehouse_location | +| `outbound_mng` | 출고 관리 | warehouse_location | +| `material_release` | 자재 출고 | - | + +#### 물류 관리 + +| 테이블 | 역할 | +|--------|------| +| `shipment_header` | 출하 헤더 | +| `shipment_detail` | 출하 상세 | +| `shipment_plan` | 출하 계획 | +| `shipment_instruction` | 출하 지시 | +| `shipment_instruction_item` | 출하 지시 품목 | +| `shipment_pallet` | 출하 파레트 | +| `delivery_destination` | 납품처 정보 | +| `delivery_route_mng` | 배송 경로 관리 | +| `delivery_route_mng_log` | 배송 경로 이력 | +| `delivery_status` | 배송 상태 | + +#### 디지털 트윈 (창고 레이아웃) + +| 테이블 | 역할 | +|--------|------| +| `digital_twin_layout` | 디지털 트윈 레이아웃 | +| `digital_twin_layout_template` | 레이아웃 템플릿 | +| `digital_twin_location_layout` | Location 배치 정보 | +| `digital_twin_zone_layout` | Zone 배치 정보 | +| `digital_twin_objects` | 디지털 트윈 객체 | +| `yard_layout` | 야드 레이아웃 | +| `yard_material_placement` | 야드 자재 배치 | + +**워크플로우: 재고 관리** +``` +1. 입고 (inbound_mng) + → inventory_stock 증가 + → inventory_history 기록 + +2. 출고 (outbound_mng) + → inventory_stock 감소 + → inventory_history 기록 + +3. 창고 위치 관리 + → warehouse_location + → digital_twin_location_layout (시각화) + +4. 재고 조사 + → inventory_stock 조회 + → 실사 vs 전산 비교 + → 차이 조정 +``` + +### 6.4 생산/작업 (Production & Work) - 25+ 테이블 + +#### 핵심 테이블 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `work_orders` | 작업지시 관리 | product_mng, sales_order_mng | +| `work_orders_detail` | 작업지시 상세 | work_orders | +| `work_instruction` | 작업지시 | - | +| `work_instruction_detail` | 작업지시 상세 | work_instruction | +| `work_instruction_log` | 작업지시 이력 | work_instruction | +| `work_instruction_detail_log` | 작업지시 상세 이력 | work_instruction_detail | +| `work_order` | 작업지시 (레거시) | - | +| `work_request` | 작업 요청 (워크플로우) | - | + +#### 생산 계획 + +| 테이블 | 역할 | +|--------|------| +| `order_plan_mgmt` | 생산 계획 관리 | +| `order_plan_result_error` | 생산 계획 오류 결과 | +| `production_task` | 생산 작업 | +| `production_record` | 생산 실적 | +| `production_issue` | 생산 이슈 | +| `facility_assembly_plan` | 설비 조립 계획 | + +#### 공정 관리 + +| 테이블 | 역할 | +|--------|------| +| `process_mng` | 공정 관리 | +| `process_equipment` | 공정 설비 | +| `item_routing_version` | 품목 라우팅 버전 | +| `item_routing_detail` | 품목 라우팅 상세 | +| `input_resource` | 투입 자원 | + +#### 설비 관리 + +| 테이블 | 역할 | +|--------|------| +| `equipment_mng` | 설비 관리 | +| `equipment_mng_log` | 설비 변경 이력 | +| `equipment_consumable` | 설비 소모품 | +| `equipment_consumable_log` | 소모품 이력 | +| `equipment_inspection_item` | 설비 점검 항목 | +| `equipment_inspection_item_log` | 점검 항목 이력 | +| `inspection_equipment_mng` | 검사 설비 관리 | +| `inspection_equipment_mng_log` | 검사 설비 이력 | + +**워크플로우: 생산 프로세스** +``` +1. 수주 확정 (sales_order_mng) +2. 생산 계획 생성 (order_plan_mgmt) +3. 자재 소요 계획 (MRP) +4. 작업지시 발행 (work_orders) +5. 자재 출고 (material_release) +6. 생산 실행 (production_record) +7. 품질 검사 (inspection_standard) +8. 완제품 입고 (inventory_stock) +``` + +### 6.5 품질/검사 (Quality & Inspection) - 15+ 테이블 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `inspection_standard` | 검사 기준 | - | +| `item_inspection_info` | 품목 검사 정보 | item_info | +| `defect_standard_mng` | 불량 기준 관리 | - | +| `defect_standard_mng_log` | 불량 기준 이력 | defect_standard_mng | +| `check_report_mng` | 검수 관리 보고서 | - | + +**품질 관리 워크플로우:** +``` +1. 입고 검사 (check_report_mng) +2. 공정 검사 (inspection_standard) +3. 최종 검사 (item_inspection_info) +4. 불량 처리 (defect_standard_mng) +5. 품질 데이터 분석 +``` + +### 6.6 물류/운송 (Logistics & Transport) - 20+ 테이블 + +#### 차량 관리 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `vehicles` | 차량 정보 | - | +| `drivers` | 운전자 정보 | - | +| `vehicle_locations` | 차량 현재 위치 | vehicles | +| `vehicle_location_history` | 차량 위치 이력 | vehicles | +| `transport_vehicle_locations` | 운행 관리 실시간 위치 | vehicles | + +#### 운송 관리 + +| 테이블 | 역할 | +|--------|------| +| `transport_logs` | 운행 이력 | +| `transport_statistics` | 일별 운행 통계 | +| `vehicle_trip_summary` | 차량 운행 요약 | +| `maintenance_schedules` | 차량 정비 일정 | + +#### 운송사 관리 + +| 테이블 | 역할 | +|--------|------| +| `carrier_mng` | 운송사 관리 | +| `carrier_mng_log` | 운송사 이력 | +| `carrier_contract_mng` | 운송사 계약 관리 | +| `carrier_contract_mng_log` | 계약 이력 | +| `carrier_vehicle_mng` | 운송사 차량 관리 | +| `carrier_vehicle_mng_log` | 차량 이력 | + +#### DTG (디지털운행기록계) + +| 테이블 | 역할 | +|--------|------| +| `dtg_management` | DTG 통합 관리 (구매/설치/점검/폐기/정산) | +| `dtg_management_log` | DTG 관리 이력 | +| `dtg_contracts` | DTG 차종별/운송사별 계약 | +| `dtg_monthly_settlements` | DTG 월별 정산 | +| `dtg_maintenance_history` | DTG 점검 이력 | + +#### 물류 비용 + +| 테이블 | 역할 | +|--------|------| +| `logistics_cost_mng` | 물류 비용 관리 | +| `logistics_cost_mng_log` | 물류 비용 이력 | + +**워크플로우: 운송 관리** +``` +1. 출하 계획 (shipment_plan) +2. 차량 배차 (vehicles) +3. 운행 시작 + → vehicle_location_history (GPS 추적) + → transport_logs 기록 +4. 배송 완료 +5. 운행 통계 집계 (transport_statistics) +6. 정산 (dtg_monthly_settlements) +``` + +### 6.7 PLM/설계 (Product Lifecycle Management) - 30+ 테이블 + +#### 제품 관리 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `product_mng` | 제품 마스터 | - | +| `product_mgmt` | 제품 관리 | - | +| `product_mgmt_model` | 제품 모델 | product_mgmt | +| `product_mgmt_price_history` | 제품 가격 이력 | product_mgmt | +| `product_mgmt_upg_master` | 제품 업그레이드 마스터 | product_mgmt | +| `product_mgmt_upg_detail` | 제품 업그레이드 상세 | product_mgmt_upg_master | +| `product_kind_spec` | 제품별 사양 관리 | - | +| `product_kind_spec_main` | 제품별 사양 메인 | - | +| `product_spec` | 제품 사양 | - | +| `product_group_mng` | 제품 그룹 관리 | - | + +#### 품목 관리 + +| 테이블 | 역할 | +|--------|------| +| `item_info` | 품목 정보 | +| `part_mng` | 부품 관리 (설계 정보 포함) | +| `part_mng_history` | 부품 변경 이력 | +| `part_mgmt` | 부품 관리 | +| `part_distribution_list` | 부품 배포 리스트 | + +#### BOM 관리 + +| 테이블 | 역할 | +|--------|------| +| `klbom_tbl` | KL BOM 테이블 | +| `part_bom_qty` | BOM 수량 관리 | +| `part_bom_report` | BOM 보고서 | +| `sales_bom_report` | 영업 BOM 보고서 | +| `sales_bom_report_part` | 영업 BOM 품목 | +| `sales_bom_part_qty` | 영업 BOM 수량 | + +#### 프로젝트 관리 + +| 테이블 | 역할 | +|--------|------| +| `pms_pjt_info` | 프로젝트 정보 | +| `pms_pjt_concept_info` | 프로젝트 개념 정보 | +| `pms_pjt_year_goal` | 프로젝트 연간 목표 | +| `pms_rel_pjt_prod` | 프로젝트-제품 관계 | +| `pms_rel_pjt_concept_prod` | 프로젝트 개념-제품 관계 | +| `pms_rel_pjt_concept_milestone` | 프로젝트 개념-마일스톤 | +| `pms_rel_prod_ref_dept` | 제품-참조부서 관계 | +| `project` | 프로젝트 (레거시) | +| `project_mgmt` | 프로젝트 관리 | +| `project_concept` | 프로젝트 개념 (레거시?) | + +#### WBS (Work Breakdown Structure) + +| 테이블 | 역할 | +|--------|------| +| `pms_wbs_task` | WBS 작업 | +| `pms_wbs_task_info` | WBS 작업 정보 | +| `pms_wbs_task_confirm` | WBS 작업 확인 | +| `pms_wbs_task_standard` | WBS 작업 표준 | +| `pms_wbs_task_standard2` | WBS 작업 표준2 | +| `pms_wbs_template` | WBS 템플릿 | + +#### 설계 관리 + +| 테이블 | 역할 | +|--------|------| +| `mold_dev_request_info` | 금형 개발 요청 | +| `structural_review_proposal` | 구조 검토 제안서 | +| `external_work_review_info` | 외주 작업 검토 정보 | +| `standard_doc_info` | 표준 문서 정보 | + +#### OEM 관리 + +| 테이블 | 역할 | +|--------|------| +| `oem_mng` | OEM 관리 | +| `oem_factory_mng` | OEM 공장 관리 | +| `oem_milestone_mng` | OEM 마일스톤 관리 | + +**워크플로우: PLM 프로세스** +``` +1. 제품 기획 (pms_pjt_concept_info) +2. 프로젝트 생성 (pms_pjt_info) +3. WBS 작업 분해 (pms_wbs_task) +4. 제품 설계 (product_mng) +5. BOM 작성 (part_bom_report) +6. 부품 관리 (part_mng) +7. 설계 검토 (structural_review_proposal) +8. 양산 전환 (production_task) +``` + +### 6.8 회계/원가 (Accounting & Costing) - 20+ 테이블 + +#### 원가 관리 + +| 테이블 | 역할 | 주요 참조 | +|--------|------|-----------| +| `material_cost` | 재료비 | - | +| `injection_cost` | 사출 원가 | - | +| `profit_loss` | 손익 관리 | - | +| `profit_loss_total` | 손익 합계 | - | +| `profit_loss_coefficient` | 손익 계수 | - | +| `profit_loss_coolingtime` | 손익 냉각 시간 | - | +| `profit_loss_depth` | 손익 깊이 | - | +| `profit_loss_lossrate` | 손익 손실률 | - | +| `profit_loss_machine` | 손익 기계 | - | +| `profit_loss_pretime` | 손익 사전 시간 | - | +| `profit_loss_srrate` | 손익 SR률 | - | +| `profit_loss_weight` | 손익 중량 | - | +| `profit_loss_total_addlist` | 손익 합계 추가 리스트 | - | +| `profit_loss_total_addlist2` | 손익 합계 추가 리스트2 | - | + +#### 자재/비용 관리 + +| 테이블 | 역할 | +|--------|------| +| `material_mng` | 자재 관리 | +| `material_master_mgmt` | 자재 마스터 관리 | +| `material_detail_mgmt` | 자재 상세 관리 | +| `input_cost_goal` | 투입 원가 목표 | + +#### 비용/투자 + +| 테이블 | 역할 | +|--------|------| +| `expense_master` | 비용 마스터 | +| `expense_detail` | 비용 상세 | +| `pms_invest_cost_mng` | 투자 비용 관리 | +| `fund_mgmt` | 자금 관리 | + +#### 세금계산서 + +| 테이블 | 역할 | +|--------|------| +| `tax_invoice` | 세금계산서 | +| `tax_invoice_item` | 세금계산서 항목 | + +#### 안전 예산 + +| 테이블 | 역할 | +|--------|------| +| `safety_budget_execution` | 안전 예산 집행 | +| `safety_incidents` | 안전 사고 | +| `safety_inspections` | 안전 점검 | +| `safety_inspections_log` | 안전 점검 이력 | + +**워크플로우: 원가 계산** +``` +1. BOM 기반 재료비 계산 (material_cost) +2. 공정별 가공비 계산 (injection_cost) +3. 간접비 배부 +4. 총 원가 집계 (profit_loss_total) +5. 판매가 대비 이익률 계산 (profit_loss) +``` + +### 6.9 기타 비즈니스 테이블 - 15+ 테이블 + +#### 공통 코드 + +| 테이블 | 역할 | +|--------|------| +| `comm_code` | 공통 코드 | +| `comm_code_history` | 공통 코드 이력 | +| `code_category` | 코드 카테고리 | +| `code_info` | 코드 정보 | + +#### 환율 + +| 테이블 | 역할 | +|--------|------| +| `comm_exchange_rate` | 환율 정보 | + +#### 게시판/댓글 + +| 테이블 | 역할 | +|--------|------| +| `chartmgmt` | 차트 관리 | +| `comments` | 댓글 | +| `inboxtask` | 받은편지함 작업 | + +#### 첨부파일 + +| 테이블 | 역할 | +|--------|------| +| `attach_file_info` | 첨부파일 정보 | +| `file_down_log` | 파일 다운로드 로그 | + +#### 승인 + +| 테이블 | 역할 | +|--------|------| +| `approval` | 승인 | + +#### 옵션 관리 + +| 테이블 | 역할 | +|--------|------| +| `option_mng` | 옵션 관리 | +| `option_price_history` | 옵션 가격 이력 | + +#### 수주 사양 + +| 테이블 | 역할 | +|--------|------| +| `order_spec_mng` | 수주 사양 관리 | +| `order_spec_mng_history` | 수주 사양 이력 | + +#### 기타 + +| 테이블 | 역할 | +|--------|------| +| `ratecal_mgmt` | 요율 계산 관리 | +| `time_sheet` | 타임시트 | +| `problem_mng` | 문제 관리 | +| `planning_issue` | 계획 이슈 | +| `used_mng` | 중고 관리 | + +--- + +## 📊 7. 대시보드 및 리포트 시스템 + +### 7.1 대시보드 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `dashboards` | 대시보드 정의 | dashboard_id, dashboard_name, company_code | +| `dashboard_elements` | 대시보드 요소 | element_id, dashboard_id, element_type, element_config (JSONB) | +| `dashboard_shares` | 대시보드 공유 | share_id, dashboard_id, shared_with_user_id | +| `dashboard_sliders` | 대시보드 슬라이더 | slider_id, slider_name, company_code | +| `dashboard_slider_items` | 슬라이더 내 대시보드 목록 | item_id, slider_id, dashboard_id, display_order | + +**대시보드 구조:** +``` +dashboard_sliders (슬라이더 그룹) + └─ dashboard_slider_items (슬라이더에 포함된 대시보드들) + └─ dashboards (개별 대시보드) + └─ dashboard_elements (차트, 위젯 등) +``` + +### 7.2 리포트 시스템 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `report_master` | 리포트 마스터 | report_id, report_name, report_type | +| `report_query` | 리포트 쿼리 | query_id, report_id, query_sql, query_params | +| `report_layout` | 리포트 레이아웃 | layout_id, report_id, layout_config (JSONB) | +| `report_template` | 리포트 템플릿 | template_id, template_name, template_config (JSONB) | +| `report_menu_mapping` | 리포트-메뉴 매핑 | mapping_id, report_id, menu_id | + +**리포트 생성 프로세스:** +``` +1. report_master 정의 +2. report_query SQL 작성 +3. report_layout 레이아웃 설정 +4. report_menu_mapping 메뉴 연결 +5. 사용자가 메뉴 클릭 +6. 동적 SQL 실행 +7. 결과를 레이아웃에 맞춰 렌더링 +``` + +--- + +## 🔧 8. 고급 기능 시스템 + +### 8.1 카테고리 시스템 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `category_column_mapping` | 카테고리 컬럼 매핑 | table_name, column_name, category_key | +| `table_column_category_values` | 카테고리 값 | table_name, column_name, company_code, category_values (JSONB) | +| `category_value_cascading_group` | 카테고리 연쇄 그룹 | group_id, group_name | +| `category_value_cascading_mapping` | 카테고리 연쇄 매핑 | mapping_id, parent_category, child_category | + +**카테고리 기능:** +- 컬럼별 드롭다운 옵션 관리 +- 회사별 독립적인 카테고리 값 +- 연쇄 드롭다운 (parent → child) + +### 8.2 연쇄 드롭다운 (Cascading Dropdown) + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `cascading_relation` | 연쇄 드롭다운 관계 | relation_id, parent_field, child_field, relation_config | +| `cascading_hierarchy_group` | 다단계 연쇄 그룹 | group_id, group_name, hierarchy_levels | +| `cascading_hierarchy_level` | 다단계 연쇄 레벨 | level_id, group_id, level_order, level_config | +| `cascading_condition` | 조건부 연쇄 | condition_id, parent_field, child_field, condition_config | +| `cascading_multi_parent` | 다중 부모 연쇄 | relation_id, child_field, parent_fields (ARRAY) | +| `cascading_multi_parent_source` | 다중 부모 소스 | source_id, relation_id, parent_field, source_config | +| `cascading_mutual_exclusion` | 상호 배제 | exclusion_id, field_1, field_2 | +| `cascading_reverse_lookup` | 역방향 연쇄 | lookup_id, child_field, parent_field | +| `cascading_auto_fill_group` | 자동 입력 그룹 | group_id, master_field, auto_fill_fields | +| `cascading_auto_fill_mapping` | 자동 입력 매핑 | mapping_id, group_id, source_field, target_field | + +**연쇄 드롭다운 패턴:** +``` +1. 단순 연쇄: 대분류 → 중분류 → 소분류 +2. 다단계 연쇄: 회사 → 부서 → 팀 → 직원 +3. 조건부 연쇄: 제품군에 따라 다른 옵션 표시 +4. 다중 부모: 부품은 여러 제품에 속할 수 있음 +5. 자동 입력: 거래처 선택 시 주소/연락처 자동 입력 +``` + +### 8.3 채번 규칙 (Numbering Rules) + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `numbering_rules` | 채번 규칙 마스터 | rule_id, rule_name, table_name, column_name, company_code | +| `numbering_rule_parts` | 채번 규칙 파트 | part_id, rule_id, part_order, part_type, part_config | + +**채번 규칙 예시:** +``` +수주번호: SO-20260206-001 + - SO: 고정 접두사 + - 20260206: 날짜 (YYYYMMDD) + - 001: 일련번호 (3자리) + +발주번호: PO-{회사코드}-{YYYYMM}-{시퀀스} + - PO: 고정 접두사 + - COMPANY_A: 회사 코드 + - 202602: 년월 + - 0001: 시퀀스 (4자리) +``` + +### 8.4 엑셀 업로드 매핑 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `excel_mapping_template` | 엑셀 매핑 템플릿 | template_id, template_name, table_name, column_mapping (JSONB) | + +**엑셀 업로드 프로세스:** +``` +1. 사용자가 엑셀 업로드 +2. 헤더 행 읽기 +3. excel_mapping_template에서 매칭 템플릿 조회 +4. 컬럼 자동 매핑 +5. 데이터 검증 +6. DB 삽입 +``` + +### 8.5 데이터 관계 브리지 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `data_relationship_bridge` | 테이블 간 데이터 관계 중계 | bridge_id, source_table, source_id, target_table, target_id, relation_type | + +**용도:** +- 여러 테이블에 걸친 데이터 연결 추적 +- M:N 관계 관리 +- 데이터 통합 조회 + +--- + +## ⚙️ 9. 배치 및 자동화 시스템 + +### 9.1 배치 작업 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `batch_jobs` | 배치 작업 정의 | job_id, job_name, job_type, job_config (JSONB) | +| `batch_schedules` | 배치 스케줄 | schedule_id, job_id, cron_expression | +| `batch_execution_logs` | 배치 실행 로그 | log_id, job_id, execution_status, started_at, completed_at | +| `batch_job_executions` | 배치 작업 실행 | execution_id, job_id, execution_params | +| `batch_job_parameters` | 배치 작업 파라미터 | param_id, job_id, param_name, param_value | +| `batch_configs` | 배치 설정 | config_id, job_id, config_key, config_value | +| `batch_mappings` | 배치 매핑 | mapping_id, source_config, target_config | + +**배치 작업 예시:** +- 일일 재고 실사 +- 월말 마감 처리 +- 통계 데이터 집계 +- 외부 시스템 동기화 + +### 9.2 동적 폼 데이터 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `dynamic_form_data` | 동적 폼 데이터 | form_id, table_name, form_data (JSONB) | + +**용도:** +- 런타임에 정의된 폼의 데이터 저장 +- 유연한 데이터 구조 + +--- + +## 🌍 10. 다국어 시스템 + +### 10.1 다국어 관리 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `multi_lang_category` | 다국어 키 카테고리 (2단계 계층) | category_id, category_name, parent_category_id | +| `multi_lang_key_master` | 다국어 키 마스터 | key_id, key_name, category_id, default_text | +| `multi_lang_text` | 다국어 텍스트 | text_id, key_id, language_code, translated_text | +| `language_master` | 언어 마스터 | language_code, language_name, is_active | + +**다국어 구조:** +``` +multi_lang_category (카테고리) + └─ multi_lang_key_master (키) + └─ multi_lang_text (언어별 번역) + ├─ ko: 한국어 + ├─ en: 영어 + ├─ ja: 일본어 + └─ zh: 중국어 +``` + +**사용 예시:** +```typescript +// menu_info.lang_key = "menu.sales.order" +// multi_lang_key_master: key_name = "menu.sales.order" +// multi_lang_text: +// - ko: "수주관리" +// - en: "Sales Order" +// - ja: "受注管理" +``` + +--- + +## 📧 11. 메일 및 로그 시스템 + +### 11.1 메일 시스템 + +| 테이블 | 역할 | 핵심 컬럼 | +|--------|------|-----------| +| `mail_log` | 메일 발송 로그 | log_id, to_email, subject, body, sent_at, sent_status | + +### 11.2 로그 테이블 + +| 테이블 | 역할 | +|--------|------| +| `ddl_execution_log` | DDL 실행 로그 | +| `file_down_log` | 파일 다운로드 로그 | +| `login_access_log` | 로그인 접근 로그 | + +### 11.3 변경 이력 테이블 (Audit Log) + +모든 주요 테이블에는 `{table_name}_log` 테이블이 있습니다: + +| 원본 테이블 | 이력 테이블 | 트리거 함수 | +|-------------|-------------|-------------| +| `carrier_contract_mng` | `carrier_contract_mng_log` | `carrier_contract_mng_log_trigger_func()` | +| `carrier_mng` | `carrier_mng_log` | `carrier_mng_log_trigger_func()` | +| `carrier_vehicle_mng` | `carrier_vehicle_mng_log` | `carrier_vehicle_mng_log_trigger_func()` | +| `defect_standard_mng` | `defect_standard_mng_log` | - | +| `delivery_route_mng` | `delivery_route_mng_log` | - | +| `dtg_management` | `dtg_management_log` | - | +| `equipment_consumable` | `equipment_consumable_log` | - | +| `equipment_inspection_item` | `equipment_inspection_item_log` | - | +| `equipment_mng` | `equipment_mng_log` | - | +| `inspection_equipment_mng` | `inspection_equipment_mng_log` | - | +| `item_info_20251202` | `item_info_20251202_log` | - | +| `logistics_cost_mng` | `logistics_cost_mng_log` | - | +| `order_table` | `order_table_log` | - | +| `safety_inspections` | `safety_inspections_log` | - | +| `sales_order_detail` | `sales_order_detail_log` | - | +| `supplier_mng` | `supplier_mng_log` | - | +| `work_instruction` | `work_instruction_log` | - | +| `work_instruction_detail` | `work_instruction_detail_log` | - | + +**이력 테이블 구조:** +```sql +CREATE TABLE {table_name}_log ( + id SERIAL PRIMARY KEY, + operation_type VARCHAR(10), -- INSERT, UPDATE, DELETE + original_id VARCHAR(500), -- 원본 레코드 ID + changed_column VARCHAR(100), + old_value TEXT, + new_value TEXT, + changed_by VARCHAR(100), + ip_address VARCHAR(50), + changed_at TIMESTAMP DEFAULT NOW(), + full_row_before JSONB, -- 변경 전 전체 행 + full_row_after JSONB -- 변경 후 전체 행 +); +``` + +**트리거 자동 기록:** +```sql +CREATE TRIGGER trg_{table_name}_log +AFTER INSERT OR UPDATE OR DELETE ON {table_name} +FOR EACH ROW +EXECUTE FUNCTION {table_name}_log_trigger_func(); +``` + +--- + +## 🔍 12. 데이터베이스 함수 및 트리거 + +### 12.1 주요 함수 + +#### 화면 관련 + +```sql +-- 화면 생성 시 메뉴 자동 생성 +CREATE FUNCTION auto_create_menu_for_screen() RETURNS TRIGGER; + +-- 화면 삭제 시 메뉴 비활성화 +CREATE FUNCTION auto_deactivate_menu_for_screen() RETURNS TRIGGER; +``` + +#### 통계 집계 + +```sql +-- 일일 운송 통계 집계 함수 +CREATE FUNCTION aggregate_daily_transport_statistics(target_date DATE DEFAULT CURRENT_DATE - 1) +RETURNS INTEGER; +``` + +#### 거리 계산 + +```sql +-- Haversine 거리 계산 (GPS 좌표) +CREATE FUNCTION calculate_distance(lat1 NUMERIC, lng1 NUMERIC, lat2 NUMERIC, lng2 NUMERIC) +RETURNS NUMERIC; + +-- 차량 위치 이력의 이전 위치로부터 거리 계산 +CREATE FUNCTION calculate_distance_from_prev() RETURNS TRIGGER; +``` + +#### 비즈니스 로직 + +```sql +-- 수주 잔량 자동 계산 +CREATE FUNCTION calculate_order_balance() RETURNS TRIGGER; + +-- 세금계산서 합계 자동 계산 +CREATE FUNCTION calculate_tax_invoice_total() RETURNS TRIGGER; + +-- 영업에서 프로젝트 자동 생성 +CREATE FUNCTION auto_create_project_from_sales(p_sales_no VARCHAR) RETURNS VARCHAR; + +-- 차량 상태 변경 시 운행 통계 계산 +CREATE FUNCTION calculate_trip_on_status_change() RETURNS TRIGGER; +``` + +### 12.2 주요 트리거 + +```sql +-- 화면 생성 시 메뉴 자동 생성 +CREATE TRIGGER trg_auto_create_menu_for_screen +AFTER INSERT ON screen_definitions +FOR EACH ROW +EXECUTE FUNCTION auto_create_menu_for_screen(); + +-- 화면 삭제 시 메뉴 비활성화 +CREATE TRIGGER trg_auto_deactivate_menu_for_screen +AFTER UPDATE ON screen_definitions +FOR EACH ROW +EXECUTE FUNCTION auto_deactivate_menu_for_screen(); + +-- 차량 위치 이력 거리 자동 계산 +CREATE TRIGGER trg_calculate_distance_from_prev +BEFORE INSERT ON vehicle_location_history +FOR EACH ROW +EXECUTE FUNCTION calculate_distance_from_prev(); + +-- 수주 잔량 자동 계산 +CREATE TRIGGER trg_calculate_order_balance +BEFORE INSERT OR UPDATE ON orders +FOR EACH ROW +EXECUTE FUNCTION calculate_order_balance(); + +-- 세금계산서 합계 자동 계산 +CREATE TRIGGER trg_calculate_tax_invoice_total +BEFORE INSERT OR UPDATE ON tax_invoice +FOR EACH ROW +EXECUTE FUNCTION calculate_tax_invoice_total(); +``` + +--- + +## 📈 13. 인덱스 전략 + +### 13.1 필수 인덱스 + +**모든 테이블:** +```sql +-- company_code 인덱스 (멀티테넌시 필수) +CREATE INDEX idx_{table_name}_company_code ON {table_name}(company_code); +``` + +### 13.2 복합 인덱스 + +**자주 함께 조회되는 컬럼:** +```sql +-- 회사별, 날짜별 조회 +CREATE INDEX idx_sales_company_date ON sales_order_mng(company_code, order_date DESC); + +-- 회사별, 상태별 조회 +CREATE INDEX idx_work_orders_company_status ON work_orders(company_code, status); + +-- 회사별, 거래처별 조회 +CREATE INDEX idx_purchase_company_supplier ON purchase_order_master(company_code, partner_objid); +``` + +### 13.3 부분 인덱스 + +**특정 조건의 데이터만:** +```sql +-- 활성 상태의 메뉴만 인덱싱 +CREATE INDEX idx_menu_active ON menu_info(company_code, menu_type) +WHERE status = 'active'; + +-- 미완료 작업지시만 인덱싱 +CREATE INDEX idx_work_orders_pending ON work_orders(company_code, wo_number) +WHERE status IN ('PENDING', 'IN_PROGRESS'); +``` + +### 13.4 JSONB 인덱스 + +**JSONB 컬럼 검색:** +```sql +-- GIN 인덱스 (JSONB 전체 검색) +CREATE INDEX idx_screen_layouts_config ON screen_layouts USING GIN (layout_config); + +-- JSONB 특정 키 인덱스 +CREATE INDEX idx_flow_step_action_type ON flow_step ((action_config->>'action_type')); +``` + +--- + +## 🔒 14. 데이터베이스 보안 + +### 14.1 암호화 컬럼 + +```sql +-- 외부 DB 비밀번호 암호화 +external_db_connections.password_encrypted TEXT + +-- 외부 REST API 인증 정보 암호화 +external_rest_api_connections.auth_config JSONB +``` + +**암호화 방식:** +- AES-256-GCM +- 애플리케이션 레벨에서 암호화/복호화 +- DB에는 암호화된 값만 저장 + +### 14.2 접근 제어 + +**회사별 데이터 격리:** +```sql +-- 모든 쿼리에 company_code 필터 필수 +WHERE company_code = $1 AND company_code != '*' +``` + +**사용자별 권한 관리:** +``` +user_info + → authority_sub_user + → authority_master + → rel_menu_auth + → 메뉴별 CRUD 권한 +``` + +### 14.3 감사 로그 + +- 모든 변경 사항은 `{table_name}_log` 테이블에 기록 +- IP 주소, 사용자 ID, 변경 시각 추적 +- 변경 전후 전체 행 데이터 JSONB로 저장 + +--- + +## 🚀 15. 성능 최적화 전략 + +### 15.1 쿼리 최적화 + +**1. company_code 필터링 항상 포함** +```sql +-- ✅ Good +SELECT * FROM sales_order_mng +WHERE company_code = 'COMPANY_A' + AND order_date >= '2026-01-01'; + +-- ❌ Bad (전체 스캔) +SELECT * FROM sales_order_mng +WHERE order_date >= '2026-01-01'; +``` + +**2. JOIN 시 company_code 매칭** +```sql +-- ✅ Good +SELECT so.*, c.customer_name +FROM sales_order_mng so +LEFT JOIN customer_mng c + ON so.customer_code = c.customer_code + AND so.company_code = c.company_code -- 필수! +WHERE so.company_code = 'COMPANY_A'; + +-- ❌ Bad (크로스 조인 발생) +SELECT so.*, c.customer_name +FROM sales_order_mng so +LEFT JOIN customer_mng c + ON so.customer_code = c.customer_code +WHERE so.company_code = 'COMPANY_A'; +``` + +**3. 인덱스 활용** +```sql +-- 복합 인덱스 순서 중요 +CREATE INDEX idx_sales_company_date_status +ON sales_order_mng(company_code, order_date, status); + +-- ✅ Good (인덱스 활용) +WHERE company_code = 'COMPANY_A' + AND order_date >= '2026-01-01' + AND status = 'CONFIRMED'; + +-- ❌ Bad (인덱스 미활용) +WHERE status = 'CONFIRMED' + AND order_date >= '2026-01-01' + AND company_code = 'COMPANY_A'; +``` + +### 15.2 대용량 데이터 처리 + +**파티셔닝:** +```sql +-- 날짜 기반 파티셔닝 (예시) +CREATE TABLE vehicle_location_history ( + ... +) PARTITION BY RANGE (recorded_at); + +CREATE TABLE vehicle_location_history_2026_01 +PARTITION OF vehicle_location_history +FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'); +``` + +**배치 처리:** +```sql +-- 대량 삽입 시 COPY 사용 +COPY table_name FROM '/path/to/file.csv' WITH (FORMAT csv, HEADER true); + +-- 대량 업데이트 시 배치 단위로 +UPDATE table_name +SET status = 'PROCESSED' +WHERE id IN ( + SELECT id FROM table_name + WHERE status = 'PENDING' + LIMIT 1000 +); +``` + +### 15.3 캐싱 전략 + +**애플리케이션 레벨 캐싱:** +- 메타데이터 (table_labels, column_labels) → Redis +- 공통 코드 (comm_code) → Redis +- 메뉴 정보 (menu_info) → Redis +- 사용자 권한 (rel_menu_auth) → Redis + +**쿼리 결과 캐싱:** +- 통계 데이터 +- 집계 데이터 +- 읽기 전용 마스터 데이터 + +--- + +## 📝 16. 마이그레이션 가이드 + +### 16.1 마이그레이션 파일 목록 + +``` +db/migrations/ +├── 037_add_parent_group_to_screen_groups.sql +├── 050_create_work_orders_table.sql +├── 051_insert_work_order_screen_definition.sql +├── 052_insert_work_order_screen_layout.sql +├── 054_create_screen_management_enhancement.sql +├── 055_create_customer_item_prices_table.sql +└── plm_schema_20260120.sql (전체 스키마 덤프) +``` + +### 16.2 마이그레이션 실행 순서 + +**1. 테이블 생성** +```sql +CREATE TABLE {table_name} ( + id VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + company_code VARCHAR(20) NOT NULL, + created_date TIMESTAMP DEFAULT NOW(), + updated_date TIMESTAMP DEFAULT NOW(), + writer VARCHAR(500), + -- 비즈니스 컬럼들... +); +``` + +**2. 인덱스 생성** +```sql +CREATE INDEX idx_{table_name}_company_code ON {table_name}(company_code); +CREATE INDEX idx_{table_name}_created_date ON {table_name}(created_date DESC); +-- 기타 필요한 인덱스들... +``` + +**3. 메타데이터 등록** +```sql +-- table_labels +INSERT INTO table_labels (table_name, table_label, description) +VALUES ('{table_name}', '{한글명}', '{설명}') +ON CONFLICT (table_name) DO UPDATE SET ...; + +-- table_type_columns +INSERT INTO table_type_columns (table_name, column_name, company_code, input_type, ...) +VALUES ...; + +-- column_labels (레거시 호환) +INSERT INTO column_labels (table_name, column_name, column_label, ...) +VALUES ...; +``` + +**4. 코멘트 추가** +```sql +COMMENT ON TABLE {table_name} IS '{테이블 설명}'; +COMMENT ON COLUMN {table_name}.{column_name} IS '{컬럼 설명}'; +``` + +**5. 화면 정의 (선택)** +```sql +-- screen_definitions +INSERT INTO screen_definitions (screen_code, screen_name, table_name, ...) +VALUES ...; + +-- 트리거 자동 발동 → menu_info 자동 생성 +``` + +### 16.3 마이그레이션 롤백 + +```sql +-- 화면 정의 삭제 +DELETE FROM screen_definitions WHERE screen_code = '{screen_code}'; + +-- 메뉴 삭제 (자동 비활성화되었을 것) +DELETE FROM menu_info WHERE screen_code = '{screen_code}'; + +-- 메타데이터 삭제 +DELETE FROM column_labels WHERE table_name = '{table_name}'; +DELETE FROM table_type_columns WHERE table_name = '{table_name}'; +DELETE FROM table_labels WHERE table_name = '{table_name}'; + +-- 인덱스 삭제 +DROP INDEX IF EXISTS idx_{table_name}_company_code; + +-- 테이블 삭제 +DROP TABLE IF EXISTS {table_name}; +``` + +--- + +## 🎯 17. 데이터베이스 설계 원칙 요약 + +### 17.1 ABSOLUTE MUST (절대 필수) + +1. **모든 테이블에 company_code VARCHAR(20) NOT NULL** +2. **모든 쿼리에 company_code 필터 포함** +3. **JOIN 시 company_code 매칭 조건 포함** +4. **모든 테이블에 company_code 인덱스 생성** +5. **일반 회사는 company_code != '*' 필터 필수** + +### 17.2 표준 테이블 구조 + +```sql +CREATE TABLE {table_name} ( + -- 기본 컬럼 (표준 5종 세트) + id VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + created_date TIMESTAMP DEFAULT NOW(), + updated_date TIMESTAMP DEFAULT NOW(), + writer VARCHAR(500), + company_code VARCHAR(20) NOT NULL, + + -- 비즈니스 컬럼들 + ... +); + +-- 필수 인덱스 +CREATE INDEX idx_{table_name}_company_code ON {table_name}(company_code); +``` + +### 17.3 메타데이터 등록 필수 + +동적 테이블 생성 시: +1. `table_labels` 등록 +2. `table_type_columns` 등록 (회사별) +3. `column_labels` 등록 (레거시 호환) +4. 코멘트 추가 + +### 17.4 쿼리 패턴 + +```sql +-- ✅ 표준 SELECT +SELECT * FROM table_name +WHERE company_code = $1 + AND company_code != '*' +ORDER BY created_date DESC; + +-- ✅ 표준 JOIN +SELECT a.*, b.name +FROM table_a a +LEFT JOIN table_b b + ON a.ref_id = b.id + AND a.company_code = b.company_code -- 필수! +WHERE a.company_code = $1; + +-- ✅ 표준 집계 +SELECT + category, + COUNT(*) as total, + SUM(amount) as total_amount +FROM sales +WHERE company_code = $1 +GROUP BY category; +``` + +--- + +## 📚 18. 참고 자료 + +### 18.1 관련 문서 + +``` +docs/ +├── DB_ARCHITECTURE_ANALYSIS.md -- 기존 상세 DB 분석 문서 +├── backend-architecture-analysis.md -- 백엔드 아키텍처 분석 +├── frontend-architecture-analysis.md -- 프론트엔드 아키텍처 분석 +└── kjs/ + ├── 멀티테넌시_구현_현황_분석_보고서.md + ├── 테이블_타입관리_성능최적화_결과.md + └── 카테고리_시스템_최종_완료_보고서.md +``` + +### 18.2 스키마 파일 + +``` +db/ +├── plm_schema_20260120.sql -- 전체 스키마 덤프 +└── migrations/ + ├── 037_add_parent_group_to_screen_groups.sql + ├── 050_create_work_orders_table.sql + ├── 051_insert_work_order_screen_definition.sql + ├── 052_insert_work_order_screen_layout.sql + ├── 054_create_screen_management_enhancement.sql + └── 055_create_customer_item_prices_table.sql +``` + +### 18.3 백엔드 서비스 매핑 + +```typescript +// backend-node/src/services/ + +// 화면 관리 +screenManagementService.ts → screen_definitions, screen_layouts + +// 테이블 관리 +tableManagementService.ts → table_labels, table_type_columns, column_labels + +// 메뉴 관리 +menuService.ts → menu_info, menu_screen_groups + +// 카테고리 관리 +categoryTreeService.ts → table_column_category_values + +// 플로우 관리 +flowDefinitionService.ts → flow_definition, flow_step +flowExecutionService.ts → flow_data_status, flow_audit_log + +// 데이터플로우 +dataflowService.ts → dataflow_diagrams, screen_data_flows + +// 외부 연동 +externalDbConnectionService.ts → external_db_connections +externalRestApiConnectionService.ts → external_rest_api_connections + +// 배치 +batchService.ts → batch_jobs, batch_execution_logs + +// 인증/권한 +authService.ts → user_info, auth_tokens +roleService.ts → authority_master, rel_menu_auth +``` + +--- + +## 🎬 19. 비즈니스 워크플로우 통합 예시 + +### 19.1 수주 → 생산 → 출하 전체 플로우 + +``` +[영업팀] +1. 견적 요청 접수 (estimate_mgmt) +2. 견적서 작성 및 발송 +3. 고객 승인 + +[영업팀] +4. 수주 등록 (sales_order_mng) + → sales_order_detail (품목별 상세) + → contract_mgmt (계약 체결) + +[생산관리팀] +5. 생산 계획 수립 (order_plan_mgmt) + → 자재 소요 계획 (MRP) + → 공정별 계획 + +[구매팀] +6. 구매 요청 (sales_request_master) + → 공급처 선정 (supplier_mng) + → 발주서 작성 (purchase_order_master) + → 발주서 상세 (purchase_order_part) + +[자재팀] +7. 입고 예정 (delivery_history) + → 검수 (check_report_mng) + → 입고 확정 (receiving) + → 재고 반영 (inventory_stock) + +[생산팀] +8. 작업지시 발행 (work_orders) + → 자재 출고 (material_release) + → 생산 실행 (production_record) + +[품질팀] +9. 품질 검사 (inspection_standard) + → 합격/불합격 판정 + → 완제품 입고 (inventory_stock) + +[물류팀] +10. 출하 계획 (shipment_plan) + → 출하 지시 (shipment_instruction) + → 차량 배차 (vehicles) + → 출고 처리 (outbound_mng) + +[운송팀] +11. 운행 시작 + → GPS 추적 (vehicle_location_history) + → 운행 로그 (transport_logs) + +[고객] +12. 납품 완료 + → 배송 상태 업데이트 (delivery_status) + → 세금계산서 발행 (tax_invoice) + +[재무팀] +13. 정산 + → 매출 인식 + → 원가 계산 (profit_loss) +``` + +### 19.2 데이터 흐름 추적 + +``` +플로우 정의 (flow_definition): "수주-생산-출하 플로우" + │ + ├─ Step 1: 수주 접수 (flow_step) + │ └─ 데이터: sales_order_mng + │ └─ flow_data_status: STEP_1_COMPLETED + │ + ├─ Step 2: 생산 계획 (flow_step) + │ └─ 데이터: order_plan_mgmt + │ └─ flow_data_status: STEP_2_COMPLETED + │ + ├─ Step 3: 발주 처리 (flow_step) + │ └─ 데이터: purchase_order_master + │ └─ flow_data_status: STEP_3_COMPLETED + │ + ├─ Step 4: 입고 처리 (flow_step) + │ └─ 데이터: receiving, inventory_stock + │ └─ flow_data_status: STEP_4_COMPLETED + │ + ├─ Step 5: 생산 실행 (flow_step) + │ └─ 데이터: work_orders, production_record + │ └─ flow_data_status: STEP_5_COMPLETED + │ + └─ Step 6: 출하 완료 (flow_step) + └─ 데이터: shipment_plan, outbound_mng + └─ flow_data_status: COMPLETED + +각 단계 변경 이력: flow_audit_log +외부 시스템 연동: flow_integration_log +``` + +--- + +**문서 작성자**: Cursor AI (DB Specialist Agent) +**문서 버전**: 2.0 +**작성일**: 2026-02-06 +**기반 스키마**: plm_schema_20260120.sql (337 테이블) +**목적**: WACE ERP 전체 워크플로우 문서화를 위한 DB 구조 분석 + +--- diff --git a/docs/WACE_SYSTEM_WORKFLOW.md b/docs/WACE_SYSTEM_WORKFLOW.md new file mode 100644 index 00000000..b9cd9f23 --- /dev/null +++ b/docs/WACE_SYSTEM_WORKFLOW.md @@ -0,0 +1,955 @@ +# WACE ERP 시스템 전체 워크플로우 문서 + +> 작성일: 2026-02-06 +> 분석 방법: Multi-Agent System (Backend + Frontend + DB 전문가 병렬 분석) + +--- + +## 목차 + +1. [시스템 개요](#1-시스템-개요) +2. [기술 스택](#2-기술-스택) +3. [전체 아키텍처](#3-전체-아키텍처) +4. [백엔드 아키텍처](#4-백엔드-아키텍처) +5. [프론트엔드 아키텍처](#5-프론트엔드-아키텍처) +6. [데이터베이스 구조](#6-데이터베이스-구조) +7. [인증/인가 워크플로우](#7-인증인가-워크플로우) +8. [화면 디자이너 워크플로우](#8-화면-디자이너-워크플로우) +9. [사용자 업무 워크플로우](#9-사용자-업무-워크플로우) +10. [플로우 엔진 워크플로우](#10-플로우-엔진-워크플로우) +11. [데이터플로우 시스템](#11-데이터플로우-시스템) +12. [대시보드 시스템](#12-대시보드-시스템) +13. [배치/스케줄 시스템](#13-배치스케줄-시스템) +14. [멀티테넌시 아키텍처](#14-멀티테넌시-아키텍처) +15. [외부 연동](#15-외부-연동) +16. [배포 환경](#16-배포-환경) + +--- + +## 1. 시스템 개요 + +WACE는 **로우코드(Low-Code) ERP 플랫폼**이다. 관리자가 코드 없이 드래그앤드롭으로 업무 화면을 설계하면, 사용자는 해당 화면으로 바로 업무를 처리할 수 있는 구조다. + +### 핵심 컨셉 + +``` +관리자 → 화면 디자이너로 화면 설계 → 메뉴에 연결 + ↓ +사용자 → 메뉴 클릭 → 화면 자동 렌더링 → 업무 수행 +``` + +### 주요 특징 + +- **드래그앤드롭 화면 디자이너**: 코드 없이 UI 구성 +- **동적 컴포넌트 시스템**: V2 통합 컴포넌트 10종으로 모든 UI 표현 +- **플로우 엔진**: 워크플로우(승인, 이동 등) 자동화 +- **데이터플로우**: 비즈니스 로직을 비주얼 다이어그램으로 설계 +- **멀티테넌시**: 회사별 완벽한 데이터 격리 +- **다국어 지원**: KR/EN/CN 다국어 라벨 관리 + +--- + +## 2. 기술 스택 + +| 영역 | 기술 | 비고 | +|------|------|------| +| **Frontend** | Next.js 15 (App Router) | React 19, TypeScript | +| **UI 라이브러리** | shadcn/ui + Radix UI | Tailwind CSS 4 | +| **상태 관리** | React Context + Zustand | React Query (서버 상태) | +| **Backend** | Node.js + Express | TypeScript | +| **Database** | PostgreSQL | Raw Query (ORM 미사용) | +| **인증** | JWT | 자동 갱신, 세션 관리 | +| **빌드/배포** | Docker | dev/prod 분리 | +| **포트** | FE: 9771(dev)/5555(prod) | BE: 8080 | + +--- + +## 3. 전체 아키텍처 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 사용자 브라우저 │ +│ Next.js App (React 19 + shadcn/ui + Tailwind CSS) │ +│ ├── 인증: JWT + Cookie + localStorage │ +│ ├── 상태: Context + Zustand + React Query │ +│ └── API: Axios Client (lib/api/) │ +└──────────────────────────┬──────────────────────────────────────┘ + │ HTTP/JSON (JWT Bearer Token) + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ Express Backend (Node.js) │ +│ ├── Middleware: Helmet → CORS → RateLimit → Auth → Permission │ +│ ├── Routes: 60+ 모듈 │ +│ ├── Controllers: 69개 │ +│ ├── Services: 87개 │ +│ └── Database: pg Pool (Raw Query) │ +└──────────────────────────┬──────────────────────────────────────┘ + │ TCP/SQL + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ PostgreSQL Database │ +│ ├── 시스템 테이블: 사용자, 회사, 메뉴, 권한, 화면 │ +│ ├── 메타데이터: 테이블/컬럼 정의, 코드, 카테고리 │ +│ ├── 비즈니스: 동적 생성 테이블 (화면별) │ +│ └── 멀티테넌시: 모든 테이블에 company_code │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. 백엔드 아키텍처 + +### 4.1 디렉토리 구조 + +``` +backend-node/src/ +├── app.ts # Express 앱 진입점 +├── config/ # 환경설정, Multer +├── controllers/ # 69개 컨트롤러 +├── services/ # 87개 서비스 +├── routes/ # 60+ 라우트 모듈 +├── middleware/ # 인증, 권한, 에러 처리 +│ ├── authMiddleware.ts # JWT 인증 +│ ├── permissionMiddleware.ts # 3단계 권한 체크 +│ ├── superAdminMiddleware.ts # 슈퍼관리자 전용 +│ └── errorHandler.ts # 전역 에러 처리 +├── database/ # DB 연결, 커넥터 팩토리 +│ ├── db.ts # PostgreSQL Pool +│ ├── DatabaseConnectorFactory.ts +│ ├── PostgreSQLConnector.ts +│ ├── MySQLConnector.ts +│ └── MariaDBConnector.ts +├── types/ # TypeScript 타입 (26개) +└── utils/ # 유틸리티 (16개) +``` + +### 4.2 미들웨어 스택 (실행 순서) + +``` +요청 → Helmet (보안 헤더) + → Compression (응답 압축) + → Body Parser (JSON/URLEncoded, 10MB) + → CORS (교차 출처 허용) + → Rate Limiter (10,000 req/min) + → Token Refresh (자동 갱신) + → Route Handlers (비즈니스 로직) + → Error Handler (전역 에러 처리) +``` + +### 4.3 API 라우트 도메인별 분류 + +#### 인증/사용자 관리 +| 라우트 | 역할 | +|--------|------| +| `/api/auth` | 로그인, 로그아웃, 토큰 갱신, 회사 전환 | +| `/api/admin/users` | 사용자 CRUD, 비밀번호 초기화, 상태 변경 | +| `/api/company-management` | 회사 CRUD | +| `/api/departments` | 부서 관리 | +| `/api/roles` | 권한 그룹 관리 | + +#### 화면/메뉴 관리 +| 라우트 | 역할 | +|--------|------| +| `/api/screen-management` | 화면 정의 CRUD, 그룹, 파일, 임베딩 | +| `/api/admin/menus` | 메뉴 트리 CRUD, 화면 할당 | +| `/api/table-management` | 테이블 CRUD, 엔티티 조인, 카테고리 | +| `/api/common-codes` | 공통 코드/카테고리 관리 | +| `/api/multilang` | 다국어 키/번역 관리 | + +#### 데이터 관리 +| 라우트 | 역할 | +|--------|------| +| `/api/data` | 동적 테이블 CRUD, 조인 쿼리 | +| `/api/data/:tableName` | 특정 테이블 데이터 조회 | +| `/api/data/join` | 조인 쿼리 실행 | +| `/api/dynamic-form` | 동적 폼 데이터 저장 | +| `/api/entity-search` | 엔티티 검색 | +| `/api/entity-reference` | 엔티티 참조 | +| `/api/numbering-rules` | 채번 규칙 관리 | +| `/api/cascading-*` | 연쇄 드롭다운 관계 | + +#### 자동화 +| 라우트 | 역할 | +|--------|------| +| `/api/flow` | 플로우 정의/단계/연결/실행 | +| `/api/dataflow` | 데이터플로우 다이어그램/실행 | +| `/api/batch-configs` | 배치 작업 설정 | +| `/api/batch-management` | 배치 작업 관리 | +| `/api/batch-execution-logs` | 배치 실행 로그 | + +#### 대시보드/리포트 +| 라우트 | 역할 | +|--------|------| +| `/api/dashboards` | 대시보드 CRUD, 쿼리 실행 | +| `/api/reports` | 리포트 생성 | + +#### 외부 연동 +| 라우트 | 역할 | +|--------|------| +| `/api/external-db-connections` | 외부 DB 연결 (PostgreSQL, MySQL, MariaDB, MSSQL, Oracle) | +| `/api/external-rest-api-connections` | 외부 REST API 연결 | +| `/api/mail` | 메일 발송/수신/템플릿 | +| `/api/tax-invoice` | 세금계산서 | + +#### 특수 도메인 +| 라우트 | 역할 | +|--------|------| +| `/api/delivery` | 배송/화물 관리 | +| `/api/risk-alerts` | 위험 알림 | +| `/api/todos` | 할일 관리 | +| `/api/bookings` | 예약 관리 | +| `/api/digital-twin` | 디지털 트윈 (야드 모니터링) | +| `/api/schedule` | 스케줄 자동 생성 | +| `/api/vehicle` | 차량 운행 | +| `/api/driver` | 운전자 관리 | +| `/api/files` | 파일 업로드/다운로드 | +| `/api/ddl` | DDL 실행 (슈퍼관리자 전용) | + +### 4.4 서비스 레이어 패턴 + +```typescript +// 표준 서비스 패턴 +class ExampleService { + // 목록 조회 (멀티테넌시 적용) + async findAll(companyCode: string, filters?: any) { + if (companyCode === "*") { + // 슈퍼관리자: 전체 데이터 + return await db.query("SELECT * FROM table ORDER BY company_code"); + } else { + // 일반 사용자: 자기 회사 데이터만 + return await db.query( + "SELECT * FROM table WHERE company_code = $1", + [companyCode] + ); + } + } +} +``` + +### 4.5 에러 처리 전략 + +```typescript +// 전역 에러 핸들러 (errorHandler.ts) +- PostgreSQL 에러: 중복키(23505), 외래키(23503), 널 제약(23502) 등 +- JWT 에러: 만료, 유효하지 않은 토큰 +- 일반 에러: 500 Internal Server Error +- 개발 환경: 상세 에러 스택 포함 +- 운영 환경: 일반적인 에러 메시지만 반환 +``` + +--- + +## 5. 프론트엔드 아키텍처 + +### 5.1 디렉토리 구조 + +``` +frontend/ +├── app/ # Next.js App Router +│ ├── (auth)/ # 인증 (로그인) +│ ├── (main)/ # 메인 앱 (인증 필요) +│ ├── (pop)/ # 모바일/팝업 +│ └── (admin)/ # 특수 관리자 +├── components/ # React 컴포넌트 +│ ├── screen/ # 화면 디자이너 & 뷰어 +│ ├── admin/ # 관리 기능 +│ ├── dashboard/ # 대시보드 위젯 +│ ├── dataflow/ # 데이터플로우 디자이너 +│ ├── v2/ # V2 통합 컴포넌트 +│ ├── ui/ # shadcn/ui 기본 컴포넌트 +│ └── report/ # 리포트 디자이너 +├── lib/ +│ ├── api/ # API 클라이언트 (57개 모듈) +│ ├── registry/ # 컴포넌트 레지스트리 (482개) +│ ├── utils/ # 유틸리티 +│ └── v2-core/ # V2 코어 로직 +├── contexts/ # React Context (인증, 메뉴, 화면 등) +├── hooks/ # Custom Hooks +├── stores/ # Zustand 상태관리 +└── middleware.ts # Next.js 인증 미들웨어 +``` + +### 5.2 페이지 라우팅 구조 + +``` +/login → 로그인 +/main → 메인 대시보드 +/screens/[screenId] → 동적 화면 뷰어 (사용자) + +/admin/screenMng/screenMngList → 화면 관리 +/admin/screenMng/dashboardList → 대시보드 관리 +/admin/screenMng/reportList → 리포트 관리 +/admin/systemMng/tableMngList → 테이블 관리 +/admin/systemMng/commonCodeList → 공통코드 관리 +/admin/systemMng/dataflow → 데이터플로우 관리 +/admin/systemMng/i18nList → 다국어 관리 +/admin/userMng/userMngList → 사용자 관리 +/admin/userMng/companyList → 회사 관리 +/admin/userMng/rolesList → 권한 관리 +/admin/automaticMng/flowMgmtList → 플로우 관리 +/admin/automaticMng/batchmngList → 배치 관리 +/admin/automaticMng/mail/* → 메일 시스템 +/admin/menu → 메뉴 관리 + +/dashboard/[dashboardId] → 대시보드 뷰어 +/pop/work → 모바일 작업 화면 +``` + +### 5.3 V2 통합 컴포넌트 시스템 + +**"하나의 컴포넌트, 여러 모드"** 철학으로 설계된 10개 통합 컴포넌트: + +| 컴포넌트 | 모드 | 역할 | +|----------|------|------| +| **V2Input** | text, number, password, slider, color | 텍스트/숫자 입력 | +| **V2Select** | dropdown, radio, checkbox, tag, toggle | 선택 입력 | +| **V2Date** | date, datetime, time, range | 날짜/시간 입력 | +| **V2List** | table, card, kanban, list | 데이터 목록 표시 | +| **V2Layout** | grid, split-panel, flex | 레이아웃 구성 | +| **V2Group** | tab, accordion, section, modal | 그룹 컨테이너 | +| **V2Media** | image, video, audio, file | 미디어 표시 | +| **V2Biz** | flow, rack, numbering-rule | 비즈니스 로직 | +| **V2Hierarchy** | tree, org-chart, BOM, cascading | 계층 구조 | +| **V2Repeater** | inline-table, modal, button | 반복 데이터 | + +### 5.4 API 클라이언트 규칙 + +```typescript +// 절대 금지: fetch 직접 사용 +const res = await fetch('/api/flow/definitions'); // ❌ + +// 반드시 사용: lib/api/ 클라이언트 +import { getFlowDefinitions } from '@/lib/api/flow'; +const res = await getFlowDefinitions(); // ✅ +``` + +환경별 URL 자동 처리: +| 환경 | 프론트엔드 | 백엔드 API | +|------|-----------|-----------| +| 로컬 개발 | localhost:9771 | localhost:8080/api | +| 운영 | v1.vexplor.com | api.vexplor.com/api | + +### 5.5 상태 관리 체계 + +``` +전역 상태 +├── AuthContext → 인증/세션/토큰 +├── MenuContext → 메뉴 트리/권한 +├── ScreenPreviewContext → 프리뷰 모드 +├── ScreenMultiLangContext → 다국어 라벨 +├── TableOptionsContext → 테이블 옵션 +└── ActiveTabContext → 활성 탭 + +로컬 상태 +├── Zustand Stores → 화면 디자이너 상태, 사용자 상태 +└── React Query → 서버 데이터 캐시 (5분 stale, 30분 GC) +``` + +### 5.6 레지스트리 시스템 + +```typescript +// 컴포넌트 등록 (482개 등록됨) +ComponentRegistry.registerComponent({ + id: "v2-input", + name: "통합 입력", + category: ComponentCategory.V2, + component: V2Input, + configPanel: V2InputConfigPanel, + defaultConfig: { inputType: "text" } +}); + +// 동적 렌더링 + +``` + +--- + +## 6. 데이터베이스 구조 + +### 6.1 테이블 도메인별 분류 + +#### 사용자/인증/회사 +| 테이블 | 역할 | +|--------|------| +| `company_mng` | 회사 마스터 | +| `user_info` | 사용자 정보 | +| `user_info_history` | 사용자 변경 이력 | +| `user_dept` | 사용자-부서 매핑 | +| `dept_info` | 부서 정보 | +| `authority_master` | 권한 그룹 마스터 | +| `authority_sub_user` | 사용자-권한 매핑 | +| `login_access_log` | 로그인 로그 | + +#### 메뉴/화면 +| 테이블 | 역할 | +|--------|------| +| `menu_info` | 메뉴 트리 구조 | +| `screen_definitions` | 화면 정의 (screenId, 테이블명 등) | +| `screen_layouts_v2` | V2 레이아웃 (JSON) | +| `screen_layouts` | V1 레이아웃 (레거시) | +| `screen_groups` | 화면 그룹 (계층구조) | +| `screen_group_screens` | 화면-그룹 매핑 | +| `screen_menu_assignments` | 화면-메뉴 할당 | +| `screen_field_joins` | 화면 필드 조인 설정 | +| `screen_data_flows` | 화면 데이터 플로우 | +| `screen_table_relations` | 화면-테이블 관계 | + +#### 메타데이터 +| 테이블 | 역할 | +|--------|------| +| `table_type_columns` | 테이블 타입별 컬럼 정의 (회사별) | +| `table_column_category_values` | 컬럼 카테고리 값 | +| `code_category` | 공통 코드 카테고리 | +| `code_info` | 공통 코드 값 | +| `category_column_mapping` | 카테고리-컬럼 매핑 | +| `cascading_relation` | 연쇄 드롭다운 관계 | +| `numbering_rules` | 채번 규칙 | +| `numbering_rule_parts` | 채번 규칙 파트 | + +#### 플로우/자동화 +| 테이블 | 역할 | +|--------|------| +| `flow_definition` | 플로우 정의 | +| `flow_step` | 플로우 단계 | +| `flow_step_connection` | 플로우 단계 연결 | +| `node_flows` | 노드 플로우 (버튼 액션) | +| `dataflow_diagrams` | 데이터플로우 다이어그램 | +| `batch_definitions` | 배치 작업 정의 | +| `batch_schedules` | 배치 스케줄 | +| `batch_execution_logs` | 배치 실행 로그 | + +#### 외부 연동 +| 테이블 | 역할 | +|--------|------| +| `external_db_connections` | 외부 DB 연결 정보 | +| `external_rest_api_connections` | 외부 REST API 연결 | + +#### 다국어 +| 테이블 | 역할 | +|--------|------| +| `multi_lang_key_master` | 다국어 키 마스터 | + +#### 기타 +| 테이블 | 역할 | +|--------|------| +| `work_history` | 작업 이력 | +| `todo_items` | 할일 목록 | +| `file_uploads` | 파일 업로드 | +| `ddl_audit_log` | DDL 감사 로그 | + +### 6.2 동적 테이블 생성 패턴 + +관리자가 화면 생성 시 비즈니스 테이블이 동적으로 생성된다: + +```sql +CREATE TABLE "dynamic_table_name" ( + "id" VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, + "created_date" TIMESTAMP DEFAULT now(), + "updated_date" TIMESTAMP DEFAULT now(), + "writer" VARCHAR(500), + "company_code" VARCHAR(500), -- 멀티테넌시 필수! + -- 사용자 정의 컬럼들 (모두 VARCHAR(500)) + "product_name" VARCHAR(500), + "price" VARCHAR(500), + ... +); +CREATE INDEX idx_dynamic_company ON "dynamic_table_name"(company_code); +``` + +### 6.3 테이블 관계도 + +``` +company_mng (company_code PK) + │ + ├── user_info (company_code FK) + │ ├── authority_sub_user (user_id FK) + │ └── user_dept (user_id FK) + │ + ├── menu_info (company_code) + │ └── screen_menu_assignments (menu_objid FK) + │ + ├── screen_definitions (company_code) + │ ├── screen_layouts_v2 (screen_id FK) + │ ├── screen_groups → screen_group_screens (screen_id FK) + │ └── screen_field_joins (screen_id FK) + │ + ├── authority_master (company_code) + │ └── authority_sub_user (master_objid FK) + │ + ├── flow_definition (company_code) + │ ├── flow_step (flow_id FK) + │ └── flow_step_connection (flow_id FK) + │ + └── [동적 비즈니스 테이블들] (company_code) +``` + +--- + +## 7. 인증/인가 워크플로우 + +### 7.1 로그인 프로세스 + +``` +┌─── 사용자 ───┐ ┌─── 프론트엔드 ───┐ ┌─── 백엔드 ───┐ ┌─── DB ───┐ +│ │ │ │ │ │ │ │ +│ ID/PW 입력 │────→│ POST /auth/login │────→│ 비밀번호 검증 │────→│ user_info│ +│ │ │ │ │ │ │ 조회 │ +│ │ │ │ │ JWT 토큰 생성 │ │ │ +│ │ │ │←────│ 토큰 반환 │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ localStorage 저장│ │ │ │ │ +│ │ │ Cookie 저장 │ │ │ │ │ +│ │ │ /main 리다이렉트 │ │ │ │ │ +└──────────────┘ └──────────────────┘ └──────────────┘ └──────────┘ +``` + +### 7.2 JWT 토큰 관리 + +``` +토큰 저장: localStorage (주 저장소) + Cookie (SSR 미들웨어용) + +자동 갱신: +├── 10분마다 만료 시간 체크 +├── 만료 30분 전: 백그라운드 자동 갱신 +├── 401 응답 시: 즉시 갱신 시도 +└── 갱신 실패 시: /login 리다이렉트 + +세션 관리: +├── 데스크톱: 30분 비활성 → 세션 만료 (5분 전 경고) +└── 모바일: 24시간 비활성 → 세션 만료 (1시간 전 경고) +``` + +### 7.3 권한 체계 (3단계) + +``` +SUPER_ADMIN (company_code = "*") +├── 모든 회사 데이터 접근 가능 +├── DDL 실행 가능 +├── 시스템 설정 변경 +└── 다른 회사로 전환 (switch-company) + +COMPANY_ADMIN (userType = "COMPANY_ADMIN") +├── 자기 회사 데이터만 접근 +├── 사용자 관리 가능 +└── 메뉴/화면 관리 가능 + +USER (일반 사용자) +├── 자기 회사 데이터만 접근 +├── 권한 그룹에 따른 메뉴 접근 +└── 할당된 화면만 사용 가능 +``` + +--- + +## 8. 화면 디자이너 워크플로우 + +### 8.1 관리자: 화면 설계 + +``` +Step 1: 화면 생성 + └→ /admin/screenMng/screenMngList + └→ "새 화면" 클릭 → 화면명, 설명, 메인 테이블 입력 + +Step 2: 화면 디자이너 진입 (ScreenDesigner.tsx) + ├── 좌측 패널: 컴포넌트 팔레트 (V2 컴포넌트 10종) + ├── 중앙 캔버스: 드래그앤드롭 영역 + └── 우측 패널: 선택된 컴포넌트 속성 설정 + +Step 3: 컴포넌트 배치 + └→ V2Input 드래그 → 캔버스 배치 → 속성 설정: + ├── 위치: x, y 좌표 + ├── 크기: width, height + ├── 데이터 바인딩: columnName = "product_name" + ├── 라벨: "제품명" + ├── 조건부 표시: 특정 조건에서만 보이기 + └── 플로우 연결: 버튼 클릭 시 실행할 플로우 + +Step 4: 레이아웃 저장 + └→ screen_layouts_v2 테이블에 JSON 형태로 저장 + └→ Zod 스키마 검증 → V2 형식 우선, V1 호환 저장 + +Step 5: 메뉴에 화면 할당 + └→ /admin/menu → 메뉴 트리에서 "제품 관리" 선택 + └→ 화면 연결 (screen_menu_assignments) +``` + +### 8.2 화면 레이아웃 저장 구조 (V2) + +```json +{ + "version": "v2", + "components": [ + { + "id": "comp-1", + "componentType": "v2-input", + "position": { "x": 100, "y": 50 }, + "size": { "width": 200, "height": 40 }, + "config": { + "inputType": "text", + "columnName": "product_name", + "label": "제품명", + "required": true + } + }, + { + "id": "comp-2", + "componentType": "v2-list", + "position": { "x": 100, "y": 150 }, + "size": { "width": 600, "height": 400 }, + "config": { + "listType": "table", + "tableName": "products", + "columns": ["product_name", "price", "quantity"] + } + } + ] +} +``` + +--- + +## 9. 사용자 업무 워크플로우 + +### 9.1 전체 흐름 + +``` +사용자 로그인 + ↓ +메인 대시보드 (/main) + ↓ +좌측 메뉴에서 "제품 관리" 클릭 + ↓ +/screens/[screenId] 라우팅 + ↓ +InteractiveScreenViewer 렌더링 + ├── screen_definitions에서 화면 정보 로드 + ├── screen_layouts_v2에서 레이아웃 JSON 로드 + ├── V2 → Legacy 변환 (호환성) + └── 메인 테이블 데이터 자동 로드 + ↓ +컴포넌트별 렌더링 + ├── V2Input → formData 바인딩 + ├── V2List → 테이블 데이터 표시 + ├── V2Select → 드롭다운/라디오 선택 + └── Button → 플로우/액션 연결 + ↓ +사용자 인터랙션 + ├── 폼 입력 → formData 업데이트 + ├── 테이블 행 선택 → selectedRowsData 업데이트 + └── 버튼 클릭 → 플로우 실행 + ↓ +플로우 실행 (nodeFlowButtonExecutor) + ├── Step 1: 데이터 검증 + ├── Step 2: API 호출 (INSERT/UPDATE/DELETE) + ├── Step 3: 성공/실패 처리 + └── Step 4: 테이블 자동 새로고침 +``` + +### 9.2 조건부 표시 워크플로우 + +``` +관리자 설정: + "특별 할인 입력" 컴포넌트 + └→ 조건: product_type === "PREMIUM" 일 때만 표시 + +사용자 사용: + 1. 화면 진입 → evaluateConditional() 실행 + 2. product_type ≠ "PREMIUM" → "특별 할인 입력" 숨김 + 3. 사용자가 product_type을 "PREMIUM"으로 변경 + 4. formData 업데이트 → evaluateConditional() 재평가 + 5. product_type === "PREMIUM" → "특별 할인 입력" 표시! +``` + +--- + +## 10. 플로우 엔진 워크플로우 + +### 10.1 플로우 정의 (관리자) + +``` +/admin/automaticMng/flowMgmtList + ↓ +플로우 생성: + ├── 이름: "제품 승인 플로우" + ├── 테이블: "products" + └── 단계 정의: + Step 1: "신청" (requester) + Step 2: "부서장 승인" (manager) + Step 3: "최종 승인" (director) + 연결: Step 1 → Step 2 → Step 3 +``` + +### 10.2 플로우 실행 (사용자) + +``` +1. 사용자: 제품 신청 + └→ "저장" 버튼 클릭 + └→ flowApi.startFlow() → 상태: "부서장 승인 대기" + +2. 부서장: 승인 화면 + └→ V2Biz (flow) 컴포넌트 → 현재 단계 표시 + └→ [승인] 클릭 → flowApi.approveStep() + └→ 상태: "최종 승인 대기" + +3. 이사: 최종 승인 + └→ [승인] 클릭 → flowApi.approveStep() + └→ 상태: "완료" + └→ products.approval_status = "APPROVED" +``` + +### 10.3 데이터 이동 (moveData) + +``` +플로우의 핵심 동작: 데이터를 한 스텝에서 다음 스텝으로 이동 + +Step 1 (접수) → Step 2 (검토) → Step 3 (완료) + ├── 단건 이동: moveData(flowId, dataId, fromStep, toStep) + └── 배치 이동: moveBatchData(flowId, dataIds[], fromStep, toStep) +``` + +--- + +## 11. 데이터플로우 시스템 + +### 11.1 개요 + +데이터플로우는 비즈니스 로직을 **비주얼 다이어그램**으로 설계하는 시스템이다. + +``` +/admin/systemMng/dataflow + ↓ +React Flow 기반 캔버스 + ├── InputNode: 데이터 입력 (폼 데이터, 테이블 데이터) + ├── TransformNode: 데이터 변환 (매핑, 필터링, 계산) + ├── DatabaseNode: DB 조회/저장 + ├── RestApiNode: 외부 API 호출 + ├── ConditionNode: 조건 분기 + ├── LoopNode: 반복 처리 + ├── MergeNode: 데이터 합치기 + └── OutputNode: 결과 출력 +``` + +### 11.2 데이터플로우 실행 + +``` +버튼 클릭 → 데이터플로우 트리거 + ↓ +InputNode: formData 수집 + ↓ +TransformNode: 데이터 가공 + ↓ +ConditionNode: 조건 분기 (가격 > 10000?) + ├── Yes → DatabaseNode: INSERT INTO premium_products + └── No → DatabaseNode: INSERT INTO standard_products + ↓ +OutputNode: 결과 반환 → toast.success("저장 완료") +``` + +--- + +## 12. 대시보드 시스템 + +### 12.1 구조 + +``` +관리자: /admin/screenMng/dashboardList + └→ 대시보드 생성 → 위젯 추가 → 레이아웃 저장 + +사용자: /dashboard/[dashboardId] + └→ 위젯 그리드 렌더링 → 실시간 데이터 표시 +``` + +### 12.2 위젯 종류 + +| 카테고리 | 위젯 | 역할 | +|----------|------|------| +| 시각화 | CustomMetricWidget | 커스텀 메트릭 표시 | +| | StatusSummaryWidget | 상태 요약 | +| 리스트 | CargoListWidget | 화물 목록 | +| | VehicleListWidget | 차량 목록 | +| 지도 | MapTestWidget | 지도 표시 | +| | WeatherMapWidget | 날씨 지도 | +| 작업 | TodoWidget | 할일 목록 | +| | WorkHistoryWidget | 작업 이력 | +| 알림 | BookingAlertWidget | 예약 알림 | +| | RiskAlertWidget | 위험 알림 | +| 기타 | ClockWidget | 시계 | +| | CalendarWidget | 캘린더 | + +--- + +## 13. 배치/스케줄 시스템 + +### 13.1 구조 + +``` +관리자: /admin/automaticMng/batchmngList + ↓ +배치 작업 생성: + ├── 이름: "일일 재고 집계" + ├── 실행 쿼리: SQL 또는 데이터플로우 ID + ├── 스케줄: Cron 표현식 ("0 0 * * *" = 매일 자정) + └── 활성화/비활성화 + ↓ +배치 스케줄러 (batch_schedules) + ↓ +자동 실행 → 실행 로그 (batch_execution_logs) +``` + +### 13.2 배치 실행 흐름 + +``` +Cron 트리거 → 배치 정의 조회 → SQL/데이터플로우 실행 + ↓ +성공: execution_log에 "SUCCESS" 기록 +실패: execution_log에 "FAILED" + 에러 메시지 기록 +``` + +--- + +## 14. 멀티테넌시 아키텍처 + +### 14.1 핵심 원칙 + +``` +모든 비즈니스 테이블: company_code 컬럼 필수 +모든 쿼리: WHERE company_code = $1 필수 +모든 JOIN: ON a.company_code = b.company_code 필수 +모든 집계: GROUP BY company_code 필수 +``` + +### 14.2 데이터 격리 + +``` +회사 A (company_code = "COMPANY_A"): + └→ 자기 데이터만 조회/수정/삭제 가능 + +회사 B (company_code = "COMPANY_B"): + └→ 자기 데이터만 조회/수정/삭제 가능 + +슈퍼관리자 (company_code = "*"): + └→ 모든 회사 데이터 조회 가능 + └→ 일반 회사는 "*" 데이터를 볼 수 없음 + +중요: company_code = "*"는 공통 데이터가 아니라 슈퍼관리자 전용 데이터! +``` + +### 14.3 코드 패턴 + +```typescript +// 백엔드 표준 패턴 +const companyCode = req.user!.companyCode; + +if (companyCode === "*") { + // 슈퍼관리자: 전체 데이터 + query = "SELECT * FROM table ORDER BY company_code"; +} else { + // 일반 사용자: 자기 회사만, "*" 제외 + query = "SELECT * FROM table WHERE company_code = $1 AND company_code != '*'"; + params = [companyCode]; +} +``` + +--- + +## 15. 외부 연동 + +### 15.1 외부 DB 연결 + +``` +지원 DB: PostgreSQL, MySQL, MariaDB, MSSQL, Oracle + +관리: /api/external-db-connections + ├── 연결 정보 등록 (host, port, database, credentials) + ├── 연결 테스트 + ├── 쿼리 실행 + └── 데이터플로우에서 DatabaseNode로 사용 +``` + +### 15.2 외부 REST API 연결 + +``` +관리: /api/external-rest-api-connections + ├── API 엔드포인트 등록 (URL, method, headers) + ├── 인증 설정 (Bearer, Basic, API Key) + ├── 테스트 호출 + └── 데이터플로우에서 RestApiNode로 사용 +``` + +### 15.3 메일 시스템 + +``` +관리: /admin/automaticMng/mail/* + ├── 메일 템플릿 관리 + ├── 메일 발송 (개별/대량) + ├── 수신 메일 확인 + └── 발송 이력 조회 +``` + +--- + +## 16. 배포 환경 + +### 16.1 Docker 구성 + +``` +개발 환경 (Mac): +├── docker/dev/docker-compose.backend.mac.yml (BE: 8080) +└── docker/dev/docker-compose.frontend.mac.yml (FE: 9771) + +운영 환경: +├── docker/prod/docker-compose.backend.prod.yml (BE: 8080) +└── docker/prod/docker-compose.frontend.prod.yml (FE: 5555) +``` + +### 16.2 서버 정보 + +| 환경 | 서버 | 포트 | DB | +|------|------|------|-----| +| 개발 | 39.117.244.52 | FE:9771, BE:8080 | 39.117.244.52:11132 | +| 운영 | 211.115.91.141 | FE:5555, BE:8080 | 211.115.91.141:11134 | + +### 16.3 백엔드 시작 시 자동 작업 + +``` +서버 시작 (app.ts) + ├── 마이그레이션 실행 (DB 스키마 업데이트) + ├── 배치 스케줄러 초기화 + ├── 위험 알림 캐시 로드 + └── 메일 정리 Cron 시작 +``` + +--- + +## 부록: 업무 진행 요약 + +### 새로운 업무 화면을 만드는 전체 프로세스 + +``` +1. [DB] 테이블 관리에서 비즈니스 테이블 생성 + └→ 컬럼 정의, 타입 설정 + +2. [화면] 화면 관리에서 새 화면 생성 + └→ 메인 테이블 지정 + +3. [디자인] 화면 디자이너에서 UI 구성 + └→ V2 컴포넌트 배치, 데이터 바인딩 + +4. [로직] 데이터플로우 설계 (필요시) + └→ 저장/수정/삭제 로직 다이어그램 + +5. [플로우] 플로우 정의 (승인 프로세스 필요시) + └→ 단계 정의, 연결 + +6. [메뉴] 메뉴에 화면 할당 + └→ 사용자가 접근할 수 있게 메뉴 트리 배치 + +7. [권한] 권한 그룹에 메뉴 할당 + └→ 특정 사용자 그룹만 접근 가능하게 + +8. [사용] 사용자가 메뉴 클릭 → 업무 시작! +``` diff --git a/docs/backend-analysis-README.md b/docs/backend-analysis-README.md new file mode 100644 index 00000000..27694d2e --- /dev/null +++ b/docs/backend-analysis-README.md @@ -0,0 +1,246 @@ +# WACE ERP Backend - 분석 문서 인덱스 + +> **분석 완료일**: 2026-02-06 +> **분석자**: Backend Specialist + +--- + +## 📚 문서 목록 + +### 1. 📖 상세 분석 문서 +**파일**: `backend-architecture-detailed-analysis.md` +**내용**: 백엔드 전체 아키텍처 상세 분석 (16개 섹션) + +- 전체 개요 및 기술 스택 +- 디렉토리 구조 +- 미들웨어 스택 구성 +- 인증/인가 시스템 (JWT, 3단계 권한) +- 멀티테넌시 구현 방식 +- API 라우트 전체 목록 +- 비즈니스 도메인별 모듈 (8개 도메인) +- 데이터베이스 접근 방식 (Raw Query) +- 외부 시스템 연동 (DB/REST API) +- 배치/스케줄 처리 (node-cron) +- 파일 처리 (multer) +- 에러 핸들링 +- 로깅 시스템 (Winston) +- 보안 및 권한 관리 +- 성능 최적화 + +**특징**: 워크플로우 문서에 통합하기 위한 완전한 아키텍처 분석 + +--- + +### 2. 📄 요약 문서 +**파일**: `backend-architecture-summary.md` +**내용**: 백엔드 아키텍처 핵심 요약 (16개 섹션 압축) + +- 기술 스택 요약 +- 계층 구조 다이어그램 +- 디렉토리 구조 +- 미들웨어 스택 순서 +- 인증/인가 흐름도 +- 멀티테넌시 핵심 원칙 +- API 라우트 카테고리별 정리 +- 비즈니스 도메인 8개 요약 +- 데이터베이스 접근 패턴 +- 외부 연동 아키텍처 +- 배치 스케줄러 시스템 +- 파일 처리 흐름 +- 보안 정책 +- 에러 핸들링 전략 +- 로깅 구조 +- 성능 최적화 전략 +- **핵심 체크리스트** (개발 시 필수 규칙 8개) + +**특징**: 빠른 참조를 위한 간결한 요약 + +--- + +### 3. 🔗 API 라우트 완전 매핑 +**파일**: `backend-api-route-mapping.md` +**내용**: 프론트엔드 개발자용 API 엔드포인트 전체 목록 (200+개) + +#### 포함된 API 카테고리 +1. 인증 API (7개) +2. 관리자 API (15개) +3. 테이블 관리 API (30개) +4. 화면 관리 API (10개) +5. 플로우 API (15개) +6. 데이터플로우 API (10개) +7. 외부 연동 API (15개) +8. 배치 API (10개) +9. 메일 API (5개) +10. 파일 API (5개) +11. 대시보드 API (5개) +12. 공통코드 API (3개) +13. 다국어 API (3개) +14. 회사 관리 API (4개) +15. 부서 API (2개) +16. 권한 그룹 API (2개) +17. DDL 실행 API (1개) +18. 외부 API 프록시 (2개) +19. 디지털 트윈 API (3개) +20. 3D 필드 API (2개) +21. 스케줄 API (1개) +22. 채번 규칙 API (3개) +23. 엔티티 검색 API (2개) +24. To-Do API (3개) +25. 예약 요청 API (2개) +26. 리스크/알림 API (2개) +27. 헬스 체크 (1개) + +#### 각 API 정보 포함 +- HTTP 메서드 +- 엔드포인트 경로 +- 필요 권한 (공개/인증/관리자/슈퍼관리자) +- 기능 설명 +- Request Body/Query Params +- Response 형식 + +#### 추가 정보 +- Base URL (개발/운영) +- 공통 헤더 (Authorization) +- 응답 형식 (성공/에러) +- 에러 코드 목록 + +**특징**: 프론트엔드에서 API 호출 시 즉시 참조 가능 + +--- + +### 4. 📊 JSON 응답 요약 +**파일**: `backend-analysis-response.json` +**내용**: 구조화된 JSON 형식의 분석 결과 + +```json +{ + "status": "success", + "confidence": "high", + "result": { + "summary": "...", + "details": "...", + "files_affected": [...], + "key_findings": { + "architecture_pattern": "...", + "tech_stack": {...}, + "middleware_stack": [...], + "authentication_flow": {...}, + "permission_levels": {...}, + "multi_tenancy": {...}, + "business_domains": {...}, + "database_access": {...}, + "security": {...}, + "performance_optimization": {...} + }, + "critical_rules": [...] + } +} +``` + +**특징**: 프로그래밍 방식으로 분석 결과 활용 가능 + +--- + +## 🎯 핵심 요약 + +### 아키텍처 +- **패턴**: Layered Architecture (Controller → Service → Database) +- **언어**: TypeScript (Strict Mode) +- **프레임워크**: Express.js +- **데이터베이스**: PostgreSQL (Raw Query, Connection Pool) +- **인증**: JWT (24시간 만료, 자동 갱신) + +### 멀티테넌시 +```typescript +// ✅ 핵심 원칙 +const companyCode = req.user!.companyCode; // JWT에서 추출 + +if (companyCode === "*") { + // 슈퍼관리자: 모든 데이터 + query = "SELECT * FROM table ORDER BY company_code"; +} else { + // 일반 사용자: 자기 회사만 + 슈퍼관리자 숨김 + query = "SELECT * FROM table WHERE company_code = $1 AND company_code != '*'"; + params = [companyCode]; +} +``` + +### 권한 체계 (3단계) +1. **SUPER_ADMIN** (`company_code = "*"`) + - 전체 회사 데이터 접근 + - DDL 실행, 회사 생성/삭제 + +2. **COMPANY_ADMIN** (`company_code = "ILSHIN"`) + - 자기 회사 데이터만 접근 + - 사용자/설정 관리 + +3. **USER** (`company_code = "ILSHIN"`) + - 자기 회사 데이터만 접근 + - 읽기/쓰기만 + +### 주요 도메인 (8개) +1. **관리자** - 사용자/메뉴/권한 +2. **테이블/화면** - 메타데이터, 동적 화면 +3. **플로우** - 워크플로우 엔진 +4. **데이터플로우** - ERD, 관계도 +5. **외부 연동** - 외부 DB/REST API +6. **배치** - Cron 스케줄러 +7. **메일** - 발송/수신 +8. **파일** - 업로드/다운로드 + +### API 통계 +- **총 라우트**: 70+개 +- **총 API**: 200+개 +- **컨트롤러**: 70+개 +- **서비스**: 80+개 +- **미들웨어**: 4개 + +--- + +## 🚨 개발 시 필수 규칙 + +✅ **모든 쿼리에 `company_code` 필터 추가** +✅ **JWT 토큰에서 `company_code` 추출 (클라이언트 신뢰 금지)** +✅ **Parameterized Query 사용 (SQL Injection 방지)** +✅ **슈퍼관리자 데이터 숨김 (`company_code != '*'`)** +✅ **비밀번호는 bcrypt, 민감정보는 AES-256** +✅ **에러 핸들링 try/catch 필수** +✅ **트랜잭션이 필요한 경우 `transaction()` 사용** +✅ **파일 업로드는 회사별 디렉토리 분리** + +--- + +## 📁 문서 위치 + +``` +ERP-node/docs/ +├── backend-architecture-detailed-analysis.md (상세 분석, 16개 섹션) +├── backend-architecture-summary.md (요약, 간결한 참조) +├── backend-api-route-mapping.md (API 200+개 전체 매핑) +└── backend-analysis-response.json (JSON 구조화 데이터) +``` + +--- + +## 🔍 문서 사용 가이드 + +### 처음 백엔드를 이해하려면 +→ `backend-architecture-summary.md` 읽기 (20분) + +### 특정 기능을 구현하려면 +→ `backend-architecture-detailed-analysis.md`에서 해당 도메인 섹션 참조 + +### API를 호출하려면 +→ `backend-api-route-mapping.md`에서 엔드포인트 검색 + +### 워크플로우 문서에 통합하려면 +→ `backend-architecture-detailed-analysis.md` 전체 복사 + +### 프로그래밍 방식으로 활용하려면 +→ `backend-analysis-response.json` 파싱 + +--- + +**문서 버전**: 1.0 +**마지막 업데이트**: 2026-02-06 +**다음 업데이트 예정**: 신규 API 추가 시 diff --git a/docs/backend-analysis-response.json b/docs/backend-analysis-response.json new file mode 100644 index 00000000..b6b11bb1 --- /dev/null +++ b/docs/backend-analysis-response.json @@ -0,0 +1,239 @@ +{ + "status": "success", + "confidence": "high", + "result": { + "summary": "WACE ERP 백엔드 전체 아키텍처 분석 완료", + "details": "Node.js + Express + TypeScript + PostgreSQL Raw Query 기반 멀티테넌시 시스템. 70+ 라우트, 70+ 컨트롤러, 80+ 서비스로 구성된 계층형 아키텍처. JWT 인증, 3단계 권한 체계(SUPER_ADMIN/COMPANY_ADMIN/USER), company_code 기반 완전한 데이터 격리 구현.", + "files_affected": [ + "docs/backend-architecture-detailed-analysis.md (상세 분석 문서)", + "docs/backend-architecture-summary.md (요약 문서)", + "docs/backend-api-route-mapping.md (API 라우트 전체 매핑)" + ], + "key_findings": { + "architecture_pattern": "Layered Architecture (Controller → Service → Database)", + "tech_stack": { + "language": "TypeScript", + "runtime": "Node.js 20.10.0+", + "framework": "Express.js", + "database": "PostgreSQL (pg 라이브러리, Raw Query)", + "authentication": "JWT (jsonwebtoken)", + "scheduler": "node-cron", + "external_db_support": ["PostgreSQL", "MySQL", "MSSQL", "Oracle"] + }, + "directory_structure": { + "controllers": "70+ 파일 (API 요청 수신, 응답 생성)", + "services": "80+ 파일 (비즈니스 로직, 트랜잭션 관리)", + "routes": "70+ 파일 (API 라우팅)", + "middleware": "4개 (인증, 권한, 슈퍼관리자, 에러핸들러)", + "types": "26개 (TypeScript 타입 정의)", + "utils": "유틸리티 함수 (JWT, 암호화, 로거)" + }, + "middleware_stack": [ + "1. Process Level Exception Handlers", + "2. Helmet (보안 헤더)", + "3. Compression (Gzip)", + "4. Body Parser (10MB limit)", + "5. Static Files (/uploads)", + "6. CORS (credentials: true)", + "7. Rate Limiting (1분 10000회)", + "8. Token Auto Refresh (1시간 이내 만료 시 갱신)", + "9. API Routes (70+개)", + "10. 404 Handler", + "11. Error Handler" + ], + "authentication_flow": { + "step1": "로그인 요청 → AuthController.login()", + "step2": "AuthService.processLogin() → loginPwdCheck() (bcrypt 검증)", + "step3": "getPersonBeanFromSession() → 사용자 정보 조회", + "step4": "insertLoginAccessLog() → 로그인 이력 저장", + "step5": "JwtUtils.generateToken() → JWT 토큰 생성", + "step6": "응답: { token, userInfo, firstMenuPath }" + }, + "jwt_payload": { + "userId": "사용자 ID", + "userName": "사용자명", + "companyCode": "회사 코드 (멀티테넌시 키)", + "userType": "권한 레벨 (SUPER_ADMIN/COMPANY_ADMIN/USER)", + "exp": "만료 시간 (24시간)" + }, + "permission_levels": { + "SUPER_ADMIN": { + "company_code": "*", + "userType": "SUPER_ADMIN", + "capabilities": [ + "전체 회사 데이터 접근", + "DDL 실행", + "회사 생성/삭제", + "시스템 설정 변경" + ] + }, + "COMPANY_ADMIN": { + "company_code": "특정 회사 (예: ILSHIN)", + "userType": "COMPANY_ADMIN", + "capabilities": [ + "자기 회사 데이터만 접근", + "자기 회사 사용자 관리", + "회사 설정 변경" + ] + }, + "USER": { + "company_code": "특정 회사", + "userType": "USER", + "capabilities": [ + "자기 회사 데이터만 접근", + "읽기/쓰기 권한만" + ] + } + }, + "multi_tenancy": { + "principle": "모든 쿼리에 company_code 필터 필수", + "pattern": "JWT 토큰에서 company_code 추출 (클라이언트 신뢰 금지)", + "super_admin_visibility": "일반 회사 사용자에게 슈퍼관리자(company_code='*') 숨김", + "correct_pattern": "WHERE company_code = $1 AND company_code != '*'", + "wrong_pattern": "req.body.companyCode 사용 (보안 위험!)" + }, + "api_routes": { + "total_count": "200+개", + "categories": { + "인증/관리자": "15개", + "테이블/화면": "40개", + "플로우": "15개", + "데이터플로우": "5개", + "외부 연동": "15개", + "배치": "10개", + "메일": "5개", + "파일": "5개", + "기타": "90개" + } + }, + "business_domains": { + "관리자": { + "controller": "adminController.ts", + "service": "adminService.ts", + "features": ["사용자 관리", "메뉴 관리", "권한 그룹 관리", "시스템 설정"] + }, + "테이블/화면": { + "controller": "tableManagementController.ts, screenManagementController.ts", + "service": "tableManagementService.ts, screenManagementService.ts", + "features": ["테이블 메타데이터", "화면 정의", "화면 그룹", "테이블 로그", "엔티티 관계"] + }, + "플로우": { + "controller": "flowController.ts", + "service": "flowExecutionService.ts, flowDefinitionService.ts", + "features": ["워크플로우 설계", "단계 관리", "데이터 이동", "조건부 이동", "오딧 로그"] + }, + "데이터플로우": { + "controller": "dataflowController.ts, dataflowDiagramController.ts", + "service": "dataflowService.ts, dataflowDiagramService.ts", + "features": ["테이블 관계 정의", "ERD", "다이어그램 시각화", "관계 실행"] + }, + "외부 연동": { + "controller": "externalDbConnectionController.ts, externalRestApiConnectionController.ts", + "service": "externalDbConnectionService.ts, dbConnectionManager.ts", + "features": ["외부 DB 연결", "Connection Pool 관리", "REST API 프록시"] + }, + "배치": { + "controller": "batchController.ts, batchManagementController.ts", + "service": "batchService.ts, batchSchedulerService.ts", + "features": ["Cron 스케줄러", "외부 DB → 내부 DB 동기화", "컬럼 매핑", "실행 이력"] + }, + "메일": { + "controller": "mailSendSimpleController.ts, mailReceiveBasicController.ts", + "service": "mailSendSimpleService.ts, mailReceiveBasicService.ts", + "features": ["메일 발송 (nodemailer)", "메일 수신 (IMAP)", "템플릿 관리", "첨부파일"] + }, + "파일": { + "controller": "fileController.ts, screenFileController.ts", + "service": "fileSystemManager.ts", + "features": ["파일 업로드 (multer)", "파일 다운로드", "화면별 파일 관리"] + } + }, + "database_access": { + "connection_pool": { + "min": "2~5 (환경별)", + "max": "10~20 (환경별)", + "connectionTimeout": "30000ms", + "idleTimeout": "600000ms", + "statementTimeout": "60000ms" + }, + "query_patterns": { + "multi_row": "query('SELECT ...', [params])", + "single_row": "queryOne('SELECT ...', [params])", + "transaction": "transaction(async (client) => { ... })" + }, + "sql_injection_prevention": "Parameterized Query 사용 (pg 라이브러리)" + }, + "external_integration": { + "supported_databases": ["PostgreSQL", "MySQL", "MSSQL", "Oracle"], + "connector_pattern": "Factory Pattern (DatabaseConnectorFactory)", + "rest_api": "axios 기반 프록시" + }, + "batch_scheduler": { + "library": "node-cron", + "timezone": "Asia/Seoul", + "cron_examples": { + "매일 새벽 2시": "0 2 * * *", + "5분마다": "*/5 * * * *", + "평일 오전 8시": "0 8 * * 1-5" + }, + "execution_flow": [ + "1. 소스 DB에서 데이터 조회", + "2. 컬럼 매핑 적용", + "3. 타겟 DB에 INSERT/UPDATE", + "4. 실행 로그 기록" + ] + }, + "file_handling": { + "upload_path": "uploads/{company_code}/{timestamp}-{uuid}-{filename}", + "max_file_size": "10MB", + "allowed_types": ["이미지", "PDF", "Office 문서"], + "library": "multer" + }, + "security": { + "password_encryption": "bcrypt (12 rounds)", + "sensitive_data_encryption": "AES-256-CBC (외부 DB 비밀번호)", + "jwt_secret": "환경변수 관리", + "security_headers": ["Helmet (CSP, X-Frame-Options)", "CORS (credentials: true)", "Rate Limiting (1분 10000회)"], + "sql_injection_prevention": "Parameterized Query" + }, + "error_handling": { + "postgres_error_codes": { + "23505": "중복된 데이터", + "23503": "참조 무결성 위반", + "23502": "필수 입력값 누락" + }, + "process_level": { + "unhandledRejection": "로깅 (서버 유지)", + "uncaughtException": "로깅 (서버 유지, 주의)", + "SIGTERM/SIGINT": "Graceful Shutdown" + } + }, + "logging": { + "library": "Winston", + "log_files": { + "error.log": "에러만 (10MB × 5파일)", + "combined.log": "전체 로그 (10MB × 10파일)" + }, + "log_levels": "error (0) → warn (1) → info (2) → debug (5)" + }, + "performance_optimization": { + "pool_monitoring": "5분마다 상태 체크, 대기 연결 5개 이상 시 경고", + "slow_query_detection": "1초 이상 걸린 쿼리 자동 경고", + "caching": "Redis (메뉴: 10분 TTL, 공통코드: 30분 TTL)", + "compression": "Gzip (1KB 이상 응답, 레벨 6)" + } + }, + "critical_rules": [ + "✅ 모든 쿼리에 company_code 필터 추가", + "✅ JWT 토큰에서 company_code 추출 (클라이언트 신뢰 금지)", + "✅ Parameterized Query 사용 (SQL Injection 방지)", + "✅ 슈퍼관리자 데이터 숨김 (company_code != '*')", + "✅ 비밀번호는 bcrypt, 민감정보는 AES-256", + "✅ 에러 핸들링 try/catch 필수", + "✅ 트랜잭션이 필요한 경우 transaction() 사용", + "✅ 파일 업로드는 회사별 디렉토리 분리" + ] + }, + "needs_from_others": [], + "questions": [] +} diff --git a/docs/backend-api-route-mapping.md b/docs/backend-api-route-mapping.md new file mode 100644 index 00000000..972f64b1 --- /dev/null +++ b/docs/backend-api-route-mapping.md @@ -0,0 +1,542 @@ +# WACE ERP Backend - API 라우트 완전 매핑 + +> **작성일**: 2026-02-06 +> **목적**: 프론트엔드 개발자용 API 엔드포인트 전체 목록 + +--- + +## 📌 공통 규칙 + +### Base URL +``` +개발: http://localhost:8080 +운영: http://39.117.244.52:8080 +``` + +### 헤더 +```http +Content-Type: application/json +Authorization: Bearer {JWT_TOKEN} +``` + +### 응답 형식 +```json +{ + "success": true, + "message": "성공 메시지", + "data": { ... } +} + +// 에러 시 +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "details": "에러 상세" + } +} +``` + +--- + +## 1. 인증 API (`/api/auth`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/auth/login` | 공개 | 로그인 | `{ userId, password }` | `{ token, userInfo, firstMenuPath }` | +| POST | `/auth/logout` | 인증 | 로그아웃 | - | `{ success: true }` | +| GET | `/auth/me` | 인증 | 현재 사용자 정보 | - | `{ userInfo }` | +| GET | `/auth/status` | 공개 | 인증 상태 확인 | - | `{ isLoggedIn, isAdmin }` | +| POST | `/auth/refresh` | 인증 | 토큰 갱신 | - | `{ token }` | +| POST | `/auth/signup` | 공개 | 회원가입 (공차중계) | `{ userId, password, userName, phoneNumber, licenseNumber, vehicleNumber }` | `{ success: true }` | +| POST | `/auth/switch-company` | 슈퍼관리자 | 회사 전환 | `{ companyCode }` | `{ token, companyCode }` | + +--- + +## 2. 관리자 API (`/api/admin`) + +### 2.1 사용자 관리 + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/admin/users` | 관리자 | 사용자 목록 | `page, limit, search` | `{ users[], total }` | +| POST | `/admin/users` | 관리자 | 사용자 생성 | - | `{ user }` | +| PUT | `/admin/users/:userId` | 관리자 | 사용자 수정 | - | `{ user }` | +| DELETE | `/admin/users/:userId` | 관리자 | 사용자 삭제 | - | `{ success: true }` | +| GET | `/admin/users/:userId/history` | 관리자 | 사용자 이력 | - | `{ history[] }` | + +### 2.2 메뉴 관리 + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/admin/menus` | 인증 | 메뉴 목록 (트리) | `userId, userLang` | `{ menus[] }` | +| POST | `/admin/menus` | 관리자 | 메뉴 생성 | - | `{ menu }` | +| PUT | `/admin/menus/:menuId` | 관리자 | 메뉴 수정 | - | `{ menu }` | +| DELETE | `/admin/menus/:menuId` | 관리자 | 메뉴 삭제 | - | `{ success: true }` | + +### 2.3 표준 관리 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/admin/web-types` | 인증 | 웹타입 표준 목록 | `{ webTypes[] }` | +| GET | `/admin/button-actions` | 인증 | 버튼 액션 표준 | `{ buttonActions[] }` | +| GET | `/admin/component-standards` | 인증 | 컴포넌트 표준 | `{ components[] }` | +| GET | `/admin/template-standards` | 인증 | 템플릿 표준 | `{ templates[] }` | +| GET | `/admin/reports` | 인증 | 리포트 목록 | `{ reports[] }` | + +--- + +## 3. 테이블 관리 API (`/api/table-management`) + +### 3.1 테이블 메타데이터 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/table-management/tables` | 인증 | 테이블 목록 | `{ tables[] }` | +| GET | `/table-management/tables/:table/columns` | 인증 | 컬럼 목록 | `{ columns[] }` | +| GET | `/table-management/tables/:table/schema` | 인증 | 테이블 스키마 | `{ schema }` | +| GET | `/table-management/tables/:table/exists` | 인증 | 테이블 존재 여부 | `{ exists: boolean }` | +| GET | `/table-management/tables/:table/web-types` | 인증 | 웹타입 정보 | `{ webTypes }` | + +### 3.2 컬럼 설정 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | +|--------|------|------|------|--------------| +| POST | `/table-management/tables/:table/columns/:column/settings` | 인증 | 컬럼 설정 업데이트 | `{ web_type, input_type, ... }` | +| POST | `/table-management/tables/:table/columns/settings` | 인증 | 전체 컬럼 일괄 업데이트 | `{ columns[] }` | +| PUT | `/table-management/tables/:table/label` | 인증 | 테이블 라벨 설정 | `{ label }` | + +### 3.3 데이터 CRUD + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/table-management/tables/:table/data` | 인증 | 데이터 조회 (페이징) | `{ page, limit, filters, sort }` | `{ data[], total }` | +| POST | `/table-management/tables/:table/record` | 인증 | 단일 레코드 조회 | `{ conditions }` | `{ record }` | +| POST | `/table-management/tables/:table/add` | 인증 | 데이터 추가 | `{ data }` | `{ success: true, id }` | +| PUT | `/table-management/tables/:table/edit` | 인증 | 데이터 수정 | `{ conditions, data }` | `{ success: true }` | +| DELETE | `/table-management/tables/:table/delete` | 인증 | 데이터 삭제 | `{ conditions }` | `{ success: true }` | + +### 3.4 다중 테이블 저장 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | +|--------|------|------|------|--------------| +| POST | `/table-management/multi-table-save` | 인증 | 메인+서브 테이블 저장 | `{ mainTable, mainData, subTables: [{ table, data[] }] }` | + +### 3.5 로그 시스템 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | +|--------|------|------|------|--------------| +| POST | `/table-management/tables/:table/log` | 관리자 | 로그 테이블 생성 | - | +| GET | `/table-management/tables/:table/log/config` | 인증 | 로그 설정 조회 | - | +| GET | `/table-management/tables/:table/log` | 인증 | 로그 데이터 조회 | - | +| POST | `/table-management/tables/:table/log/toggle` | 관리자 | 로그 활성화/비활성화 | `{ is_active }` | + +### 3.6 엔티티 관계 + +| 메서드 | 경로 | 권한 | 기능 | Query Params | +|--------|------|------|------|--------------| +| GET | `/table-management/tables/entity-relations` | 인증 | 두 테이블 간 관계 조회 | `leftTable, rightTable` | +| GET | `/table-management/columns/:table/referenced-by` | 인증 | 현재 테이블 참조 목록 | - | + +### 3.7 카테고리 관리 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/table-management/category-columns` | 인증 | 회사별 카테고리 컬럼 | `{ categoryColumns[] }` | +| GET | `/table-management/menu/:menuObjid/category-columns` | 인증 | 메뉴별 카테고리 컬럼 | `{ categoryColumns[] }` | + +--- + +## 4. 화면 관리 API (`/api/screen-management`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/screen-management/screens` | 인증 | 화면 목록 | `page, limit` | `{ screens[], total }` | +| GET | `/screen-management/screens/:id` | 인증 | 화면 상세 | - | `{ screen }` | +| POST | `/screen-management/screens` | 관리자 | 화면 생성 | - | `{ screen }` | +| PUT | `/screen-management/screens/:id` | 관리자 | 화면 수정 | - | `{ screen }` | +| DELETE | `/screen-management/screens/:id` | 관리자 | 화면 삭제 | - | `{ success: true }` | + +### 화면 그룹 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/screen-groups` | 인증 | 화면 그룹 목록 | `{ screenGroups[] }` | +| POST | `/screen-groups` | 관리자 | 그룹 생성 | `{ group }` | + +### 화면 파일 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/screen-files` | 인증 | 화면 파일 목록 | `{ files[] }` | +| POST | `/screen-files` | 관리자 | 파일 업로드 | `{ file }` | + +--- + +## 5. 플로우 API (`/api/flow`) + +### 5.1 플로우 정의 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/flow/definitions` | 인증 | 플로우 목록 | - | `{ flows[] }` | +| GET | `/flow/definitions/:id` | 인증 | 플로우 상세 | - | `{ flow }` | +| POST | `/flow/definitions` | 인증 | 플로우 생성 | `{ name, description, targetTable }` | `{ flow }` | +| PUT | `/flow/definitions/:id` | 인증 | 플로우 수정 | `{ name, description }` | `{ flow }` | +| DELETE | `/flow/definitions/:id` | 인증 | 플로우 삭제 | - | `{ success: true }` | + +### 5.2 단계 관리 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/flow/definitions/:flowId/steps` | 인증 | 단계 목록 | - | `{ steps[] }` | +| POST | `/flow/definitions/:flowId/steps` | 인증 | 단계 생성 | `{ name, type, settings }` | `{ step }` | +| PUT | `/flow/steps/:stepId` | 인증 | 단계 수정 | `{ name, settings }` | `{ step }` | +| DELETE | `/flow/steps/:stepId` | 인증 | 단계 삭제 | - | `{ success: true }` | + +### 5.3 연결 관리 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/flow/connections/:flowId` | 인증 | 연결 목록 | - | `{ connections[] }` | +| POST | `/flow/connections` | 인증 | 연결 생성 | `{ fromStepId, toStepId, condition }` | `{ connection }` | +| DELETE | `/flow/connections/:connectionId` | 인증 | 연결 삭제 | - | `{ success: true }` | + +### 5.4 데이터 이동 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/flow/move` | 인증 | 데이터 이동 (단건) | `{ flowId, fromStepId, toStepId, recordId }` | `{ success: true }` | +| POST | `/flow/move-batch` | 인증 | 데이터 이동 (다건) | `{ flowId, fromStepId, toStepId, recordIds[] }` | `{ success: true, movedCount }` | + +### 5.5 단계 데이터 조회 + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/flow/:flowId/step/:stepId/count` | 인증 | 단계 데이터 개수 | - | `{ count }` | +| GET | `/flow/:flowId/step/:stepId/list` | 인증 | 단계 데이터 목록 | `page, limit` | `{ data[], total }` | +| GET | `/flow/:flowId/step/:stepId/column-labels` | 인증 | 컬럼 라벨 조회 | - | `{ labels }` | +| GET | `/flow/:flowId/steps/counts` | 인증 | 모든 단계 카운트 | - | `{ counts[] }` | + +### 5.6 단계 데이터 수정 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| PUT | `/flow/:flowId/step/:stepId/data/:recordId` | 인증 | 인라인 편집 | `{ data }` | `{ success: true }` | + +### 5.7 오딧 로그 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/flow/audit/:flowId/:recordId` | 인증 | 레코드별 오딧 로그 | `{ auditLogs[] }` | +| GET | `/flow/audit/:flowId` | 인증 | 플로우 전체 오딧 로그 | `{ auditLogs[] }` | + +--- + +## 6. 데이터플로우 API (`/api/dataflow`) + +### 6.1 관계 관리 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/dataflow/relationships` | 인증 | 관계 목록 | - | `{ relationships[] }` | +| POST | `/dataflow/relationships` | 인증 | 관계 생성 | `{ fromTable, toTable, fromColumn, toColumn, type }` | `{ relationship }` | +| PUT | `/dataflow/relationships/:id` | 인증 | 관계 수정 | `{ name, type }` | `{ relationship }` | +| DELETE | `/dataflow/relationships/:id` | 인증 | 관계 삭제 | - | `{ success: true }` | + +### 6.2 다이어그램 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/dataflow-diagrams` | 인증 | 다이어그램 목록 | - | `{ diagrams[] }` | +| GET | `/dataflow-diagrams/:id` | 인증 | 다이어그램 상세 | - | `{ diagram }` | +| POST | `/dataflow-diagrams` | 인증 | 다이어그램 생성 | `{ name, description }` | `{ diagram }` | + +### 6.3 실행 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/dataflow` | 인증 | 데이터플로우 실행 | `{ relationshipId, params }` | `{ result[] }` | + +--- + +## 7. 외부 연동 API + +### 7.1 외부 DB 연결 (`/api/external-db-connections`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/external-db-connections` | 인증 | 연결 목록 | - | `{ connections[] }` | +| GET | `/external-db-connections/:id` | 인증 | 연결 상세 | - | `{ connection }` | +| POST | `/external-db-connections` | 관리자 | 연결 생성 | `{ connectionName, dbType, host, port, database, username, password }` | `{ connection }` | +| PUT | `/external-db-connections/:id` | 관리자 | 연결 수정 | `{ connectionName, ... }` | `{ connection }` | +| DELETE | `/external-db-connections/:id` | 관리자 | 연결 삭제 | - | `{ success: true }` | +| POST | `/external-db-connections/:id/test` | 인증 | 연결 테스트 | - | `{ success: boolean, message }` | +| GET | `/external-db-connections/:id/tables` | 인증 | 테이블 목록 조회 | - | `{ tables[] }` | +| GET | `/external-db-connections/:id/tables/:table/columns` | 인증 | 컬럼 목록 조회 | - | `{ columns[] }` | + +### 7.2 외부 REST API (`/api/external-rest-api-connections`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/external-rest-api-connections` | 인증 | API 연결 목록 | - | `{ connections[] }` | +| POST | `/external-rest-api-connections` | 관리자 | API 연결 생성 | `{ name, baseUrl, authType, ... }` | `{ connection }` | +| POST | `/external-rest-api-connections/:id/test` | 인증 | API 테스트 | `{ endpoint, method }` | `{ response }` | + +### 7.3 멀티 커넥션 (`/api/multi-connection`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/multi-connection/query` | 인증 | 멀티 DB 쿼리 | `{ connections: [{ connectionId, sql }] }` | `{ results[] }` | + +--- + +## 8. 배치 API + +### 8.1 배치 설정 (`/api/batch-configs`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/batch-configs` | 인증 | 배치 설정 목록 | - | `{ batchConfigs[] }` | +| GET | `/batch-configs/:id` | 인증 | 배치 설정 상세 | - | `{ batchConfig }` | +| POST | `/batch-configs` | 관리자 | 배치 설정 생성 | `{ batchName, cronSchedule, sourceConnection, targetTable, mappings }` | `{ batchConfig }` | +| PUT | `/batch-configs/:id` | 관리자 | 배치 설정 수정 | `{ batchName, ... }` | `{ batchConfig }` | +| DELETE | `/batch-configs/:id` | 관리자 | 배치 설정 삭제 | - | `{ success: true }` | +| GET | `/batch-configs/connections` | 관리자 | 사용 가능한 커넥션 목록 | - | `{ connections[] }` | +| GET | `/batch-configs/connections/:type/tables` | 관리자 | 테이블 목록 조회 | - | `{ tables[] }` | + +### 8.2 배치 실행 (`/api/batch-management`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/batch-management/:id/execute` | 관리자 | 배치 즉시 실행 | - | `{ success: true, executionLogId }` | + +### 8.3 실행 이력 (`/api/batch-execution-logs`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/batch-execution-logs` | 인증 | 실행 이력 목록 | `batchConfigId, page, limit` | `{ logs[], total }` | +| GET | `/batch-execution-logs/:id` | 인증 | 실행 이력 상세 | - | `{ log }` | + +--- + +## 9. 메일 API (`/api/mail`) + +### 9.1 계정 관리 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/mail/accounts` | 인증 | 계정 목록 | - | `{ accounts[] }` | +| POST | `/mail/accounts` | 관리자 | 계정 추가 | `{ email, smtpHost, smtpPort, password }` | `{ account }` | + +### 9.2 템플릿 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/mail/templates-file` | 인증 | 템플릿 목록 | - | `{ templates[] }` | +| POST | `/mail/templates-file` | 관리자 | 템플릿 생성 | `{ name, subject, body }` | `{ template }` | + +### 9.3 발송/수신 + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/mail/send` | 인증 | 메일 발송 | `{ accountId, to, subject, body, attachments[] }` | `{ success: true, messageId }` | +| GET | `/mail/sent` | 인증 | 발송 이력 | `page, limit` | `{ mails[], total }` | +| POST | `/mail/receive` | 인증 | 메일 수신 | `{ accountId }` | `{ mails[] }` | + +--- + +## 10. 파일 API (`/api/files`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/files/upload` | 인증 | 파일 업로드 (multipart) | `FormData { file }` | `{ fileId, fileName, filePath, fileSize }` | +| GET | `/files` | 인증 | 파일 목록 | `page, limit` | `{ files[], total }` | +| GET | `/files/:id` | 인증 | 파일 정보 조회 | - | `{ file }` | +| GET | `/files/download/:id` | 인증 | 파일 다운로드 | - | `(파일 스트림)` | +| DELETE | `/files/:id` | 인증 | 파일 삭제 | - | `{ success: true }` | +| GET | `/uploads/:filename` | 공개 | 정적 파일 서빙 | - | `(파일 스트림)` | + +--- + +## 11. 대시보드 API (`/api/dashboards`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/dashboards` | 인증 | 대시보드 목록 | - | `{ dashboards[] }` | +| GET | `/dashboards/:id` | 인증 | 대시보드 상세 | - | `{ dashboard }` | +| POST | `/dashboards` | 관리자 | 대시보드 생성 | - | `{ dashboard }` | +| GET | `/dashboards/:id/widgets` | 인증 | 위젯 데이터 조회 | - | `{ widgets[] }` | + +--- + +## 12. 공통코드 API (`/api/common-codes`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/common-codes` | 인증 | 공통코드 목록 | `codeGroup` | `{ codes[] }` | +| GET | `/common-codes/:codeGroup/:code` | 인증 | 공통코드 상세 | - | `{ code }` | +| POST | `/common-codes` | 관리자 | 공통코드 생성 | `{ codeGroup, code, name }` | `{ code }` | + +--- + +## 13. 다국어 API (`/api/multilang`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/multilang` | 인증 | 다국어 키 목록 | `lang` | `{ translations{} }` | +| GET | `/multilang/:key` | 인증 | 특정 키 조회 | `lang` | `{ key, value }` | +| POST | `/multilang` | 관리자 | 다국어 추가 | `{ key, ko, en, cn }` | `{ translation }` | + +--- + +## 14. 회사 관리 API (`/api/company-management`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/company-management` | 슈퍼관리자 | 회사 목록 | - | `{ companies[] }` | +| POST | `/company-management` | 슈퍼관리자 | 회사 생성 | `{ companyCode, companyName }` | `{ company }` | +| PUT | `/company-management/:code` | 슈퍼관리자 | 회사 수정 | `{ companyName }` | `{ company }` | +| DELETE | `/company-management/:code` | 슈퍼관리자 | 회사 삭제 | - | `{ success: true }` | + +--- + +## 15. 부서 API (`/api/departments`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/departments` | 인증 | 부서 목록 (트리) | - | `{ departments[] }` | +| POST | `/departments` | 관리자 | 부서 생성 | `{ deptCode, deptName, parentDeptCode }` | `{ department }` | + +--- + +## 16. 권한 그룹 API (`/api/roles`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/roles` | 인증 | 권한 그룹 목록 | - | `{ roles[] }` | +| POST | `/roles` | 관리자 | 권한 그룹 생성 | `{ roleName, permissions[] }` | `{ role }` | + +--- + +## 17. DDL 실행 API (`/api/ddl`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/ddl` | 슈퍼관리자 | DDL 실행 | `{ sql }` | `{ success: true, result }` | + +--- + +## 18. 외부 API 프록시 (`/api/open-api`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/open-api/weather` | 인증 | 날씨 정보 조회 | `location` | `{ weather }` | +| GET | `/open-api/exchange` | 인증 | 환율 정보 조회 | `fromCurrency, toCurrency` | `{ rate }` | + +--- + +## 19. 디지털 트윈 API (`/api/digital-twin`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/digital-twin/layouts` | 인증 | 레이아웃 목록 | - | `{ layouts[] }` | +| GET | `/digital-twin/templates` | 인증 | 템플릿 목록 | - | `{ templates[] }` | +| GET | `/digital-twin/data` | 인증 | 실시간 데이터 | - | `{ data[] }` | + +--- + +## 20. 3D 필드 API (`/api/yard-layouts`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/yard-layouts` | 인증 | 필드 레이아웃 목록 | - | `{ yardLayouts[] }` | +| POST | `/yard-layouts` | 인증 | 레이아웃 저장 | `{ layout }` | `{ success: true }` | + +--- + +## 21. 스케줄 API (`/api/schedule`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/schedule` | 인증 | 스케줄 자동 생성 | `{ params }` | `{ schedule }` | + +--- + +## 22. 채번 규칙 API (`/api/numbering-rules`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/numbering-rules` | 인증 | 채번 규칙 목록 | - | `{ rules[] }` | +| POST | `/numbering-rules` | 관리자 | 규칙 생성 | `{ ruleName, prefix, format }` | `{ rule }` | +| POST | `/numbering-rules/:id/generate` | 인증 | 번호 생성 | - | `{ number }` | + +--- + +## 23. 엔티티 검색 API (`/api/entity-search`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| POST | `/entity-search` | 인증 | 엔티티 검색 | `{ table, filters, page, limit }` | `{ results[], total }` | +| GET | `/entity/:table/options` | 인증 | V2Select용 옵션 | `search, limit` | `{ options[] }` | + +--- + +## 24. To-Do API (`/api/todos`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/todos` | 인증 | To-Do 목록 | `status, assignee` | `{ todos[] }` | +| POST | `/todos` | 인증 | To-Do 생성 | `{ title, description, dueDate }` | `{ todo }` | +| PUT | `/todos/:id` | 인증 | To-Do 수정 | `{ status }` | `{ todo }` | + +--- + +## 25. 예약 요청 API (`/api/bookings`) + +| 메서드 | 경로 | 권한 | 기능 | Request Body | Response | +|--------|------|------|------|--------------|----------| +| GET | `/bookings` | 인증 | 예약 목록 | - | `{ bookings[] }` | +| POST | `/bookings` | 인증 | 예약 생성 | `{ resourceId, startTime, endTime }` | `{ booking }` | + +--- + +## 26. 리스크/알림 API (`/api/risk-alerts`) + +| 메서드 | 경로 | 권한 | 기능 | Query Params | Response | +|--------|------|------|------|--------------|----------| +| GET | `/risk-alerts` | 인증 | 리스크/알림 목록 | `priority, status` | `{ alerts[] }` | +| POST | `/risk-alerts` | 인증 | 알림 생성 | `{ title, content, priority }` | `{ alert }` | + +--- + +## 27. 헬스 체크 + +| 메서드 | 경로 | 권한 | 기능 | Response | +|--------|------|------|------|----------| +| GET | `/health` | 공개 | 서버 상태 확인 | `{ status: "OK", timestamp, uptime, environment }` | + +--- + +## 🔐 에러 코드 목록 + +| 코드 | HTTP Status | 설명 | +|------|-------------|------| +| `TOKEN_MISSING` | 401 | 인증 토큰 누락 | +| `TOKEN_EXPIRED` | 401 | 토큰 만료 | +| `INVALID_TOKEN` | 401 | 유효하지 않은 토큰 | +| `AUTHENTICATION_REQUIRED` | 401 | 인증 필요 | +| `INSUFFICIENT_PERMISSION` | 403 | 권한 부족 | +| `SUPER_ADMIN_REQUIRED` | 403 | 슈퍼관리자 권한 필요 | +| `COMPANY_ACCESS_DENIED` | 403 | 회사 데이터 접근 거부 | +| `INVALID_INPUT` | 400 | 잘못된 입력 | +| `RESOURCE_NOT_FOUND` | 404 | 리소스 없음 | +| `DUPLICATE_ENTRY` | 400 | 중복 데이터 | +| `FOREIGN_KEY_VIOLATION` | 400 | 참조 무결성 위반 | +| `SERVER_ERROR` | 500 | 서버 오류 | + +--- + +**문서 버전**: 1.0 +**마지막 업데이트**: 2026-02-06 +**총 API 개수**: 200+개 diff --git a/docs/backend-architecture-detailed-analysis.md b/docs/backend-architecture-detailed-analysis.md new file mode 100644 index 00000000..534cf7a3 --- /dev/null +++ b/docs/backend-architecture-detailed-analysis.md @@ -0,0 +1,1855 @@ +# WACE ERP Backend Architecture - 상세 분석 문서 + +> **작성일**: 2026-02-06 +> **작성자**: Backend Specialist +> **목적**: WACE ERP 시스템 백엔드 전체 아키텍처 분석 및 워크플로우 문서화 + +--- + +## 📑 목차 + +1. [전체 개요](#1-전체-개요) +2. [디렉토리 구조](#2-디렉토리-구조) +3. [기술 스택](#3-기술-스택) +4. [미들웨어 스택](#4-미들웨어-스택) +5. [인증/인가 시스템](#5-인증인가-시스템) +6. [멀티테넌시 구현](#6-멀티테넌시-구현) +7. [API 라우트 전체 목록](#7-api-라우트-전체-목록) +8. [비즈니스 도메인별 모듈](#8-비즈니스-도메인별-모듈) +9. [데이터베이스 접근 방식](#9-데이터베이스-접근-방식) +10. [외부 시스템 연동](#10-외부-시스템-연동) +11. [배치/스케줄 처리](#11-배치스케줄-처리) +12. [파일 처리](#12-파일-처리) +13. [에러 핸들링](#13-에러-핸들링) +14. [로깅 시스템](#14-로깅-시스템) +15. [보안 및 권한 관리](#15-보안-및-권한-관리) +16. [성능 최적화](#16-성능-최적화) + +--- + +## 1. 전체 개요 + +### 1.1 프로젝트 정보 +- **프로젝트명**: WACE ERP Backend (Node.js) +- **언어**: TypeScript (Strict Mode) +- **런타임**: Node.js 20.10.0+ +- **프레임워크**: Express.js +- **데이터베이스**: PostgreSQL (Raw Query 기반) +- **포트**: 8080 (기본값) + +### 1.2 아키텍처 특징 +1. **Layered Architecture**: Controller → Service → Database 3계층 구조 +2. **Multi-tenancy**: company_code 기반 완전한 데이터 격리 +3. **JWT 인증**: Stateless 토큰 기반 인증 시스템 +4. **Raw Query**: ORM 없이 PostgreSQL 직접 쿼리 (성능 최적화) +5. **Connection Pool**: pg 라이브러리 기반 안정적인 연결 관리 +6. **Type-Safe**: TypeScript 타입 시스템 적극 활용 + +### 1.3 주요 기능 +- 관리자 기능 (사용자/권한/메뉴 관리) +- 테이블/화면 메타데이터 관리 (동적 화면 생성) +- 플로우 관리 (워크플로우 엔진) +- 데이터플로우 다이어그램 (ERD/관계도) +- 외부 DB 연동 (PostgreSQL, MySQL, MSSQL, Oracle) +- 외부 REST API 연동 +- 배치 자동 실행 (Cron 스케줄러) +- 메일 발송/수신 +- 파일 업로드/다운로드 +- 다국어 지원 +- 대시보드/리포트 + +--- + +## 2. 디렉토리 구조 + +``` +backend-node/ +├── src/ +│ ├── app.ts # Express 앱 진입점 +│ ├── config/ # 환경 설정 +│ │ ├── environment.ts # 환경변수 관리 +│ │ └── multerConfig.ts # 파일 업로드 설정 +│ ├── controllers/ # 컨트롤러 (70+ 파일) +│ │ ├── authController.ts +│ │ ├── adminController.ts +│ │ ├── tableManagementController.ts +│ │ ├── flowController.ts +│ │ ├── dataflowController.ts +│ │ ├── batchController.ts +│ │ └── ... +│ ├── services/ # 비즈니스 로직 (80+ 파일) +│ │ ├── authService.ts +│ │ ├── adminService.ts +│ │ ├── tableManagementService.ts +│ │ ├── flowExecutionService.ts +│ │ ├── batchSchedulerService.ts +│ │ └── ... +│ ├── routes/ # API 라우터 (70+ 파일) +│ │ ├── authRoutes.ts +│ │ ├── adminRoutes.ts +│ │ ├── tableManagementRoutes.ts +│ │ ├── flowRoutes.ts +│ │ └── ... +│ ├── middleware/ # 미들웨어 (4개) +│ │ ├── authMiddleware.ts # JWT 인증 +│ │ ├── permissionMiddleware.ts # 권한 체크 +│ │ ├── superAdminMiddleware.ts # 슈퍼관리자 전용 +│ │ └── errorHandler.ts # 에러 핸들러 +│ ├── database/ # DB 연결 +│ │ ├── db.ts # PostgreSQL Pool 관리 +│ │ ├── DatabaseConnectorFactory.ts # 외부 DB 연결 +│ │ └── runMigration.ts # 마이그레이션 +│ ├── types/ # TypeScript 타입 정의 (26개) +│ │ ├── auth.ts +│ │ ├── batchTypes.ts +│ │ ├── flow.ts +│ │ └── ... +│ ├── utils/ # 유틸리티 함수 +│ │ ├── jwtUtils.ts # JWT 토큰 관리 +│ │ ├── permissionUtils.ts # 권한 체크 +│ │ ├── logger.ts # Winston 로거 +│ │ ├── encryptUtil.ts # 암호화/복호화 +│ │ ├── passwordEncryption.ts # 비밀번호 암호화 +│ │ └── ... +│ └── interfaces/ # 인터페이스 +│ └── DatabaseConnector.ts # DB 커넥터 인터페이스 +├── scripts/ # 스크립트 +│ ├── dev/ # 개발 환경 스크립트 +│ └── prod/ # 운영 환경 스크립트 +├── data/ # 정적 데이터 (JSON) +├── uploads/ # 업로드된 파일 +├── logs/ # 로그 파일 +├── package.json # NPM 의존성 +├── tsconfig.json # TypeScript 설정 +└── .env # 환경변수 +``` + +--- + +## 3. 기술 스택 + +### 3.1 핵심 라이브러리 + +```json +{ + "dependencies": { + "express": "^4.18.2", // 웹 프레임워크 + "pg": "^8.16.3", // PostgreSQL 클라이언트 + "jsonwebtoken": "^9.0.2", // JWT 토큰 + "bcryptjs": "^2.4.3", // 비밀번호 암호화 + "dotenv": "^16.3.1", // 환경변수 + "cors": "^2.8.5", // CORS 처리 + "helmet": "^7.1.0", // 보안 헤더 + "compression": "^1.7.4", // Gzip 압축 + "express-rate-limit": "^7.1.5", // Rate Limiting + "winston": "^3.11.0", // 로깅 + "multer": "^1.4.5-lts.1", // 파일 업로드 + "node-cron": "^4.2.1", // Cron 스케줄러 + "axios": "^1.11.0", // HTTP 클라이언트 + "nodemailer": "^6.10.1", // 메일 발송 + "imap": "^0.8.19", // 메일 수신 + "mysql2": "^3.15.0", // MySQL 연결 + "mssql": "^11.0.1", // MSSQL 연결 + "oracledb": "^6.9.0", // Oracle 연결 + "uuid": "^13.0.0", // UUID 생성 + "joi": "^17.11.0" // 데이터 검증 + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/pg": "^8.15.5", + "typescript": "^5.3.3", + "nodemon": "^3.1.10", + "ts-node": "^10.9.2", + "jest": "^29.7.0", + "prettier": "^3.1.0", + "eslint": "^8.55.0" + } +} +``` + +### 3.2 데이터베이스 연결 +- **메인 DB**: PostgreSQL (pg 라이브러리) +- **외부 DB 지원**: MySQL, MSSQL, Oracle, PostgreSQL +- **Connection Pool**: Min 2~5 / Max 10~20 +- **Timeout**: Connection 30s / Query 60s + +--- + +## 4. 미들웨어 스택 + +### 4.1 미들웨어 실행 순서 (app.ts) + +```typescript +// 1. 프로세스 레벨 예외 처리 +process.on('unhandledRejection', ...) +process.on('uncaughtException', ...) +process.on('SIGTERM', ...) +process.on('SIGINT', ...) + +// 2. 보안 미들웨어 +app.use(helmet({ + contentSecurityPolicy: { ... }, // CSP 설정 + frameguard: { ... } // Iframe 보호 +})) + +// 3. 압축 미들웨어 +app.use(compression()) + +// 4. Body Parser +app.use(express.json({ limit: '10mb' })) +app.use(express.urlencoded({ extended: true, limit: '10mb' })) + +// 5. 정적 파일 서빙 (/uploads) +app.use('/uploads', express.static(...)) + +// 6. CORS 설정 +app.use(cors({ + origin: [...], // 허용 도메인 + credentials: true, // 쿠키 포함 + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'] +})) + +// 7. Rate Limiting (1분에 10000회) +app.use('/api/', limiter) + +// 8. 토큰 자동 갱신 (1시간 이내 만료 시 갱신) +app.use('/api/', refreshTokenIfNeeded) + +// 9. API 라우터 (70+개) +app.use('/api/auth', authRoutes) +app.use('/api/admin', adminRoutes) +// ... + +// 10. 404 핸들러 +app.use('*', notFoundHandler) + +// 11. 에러 핸들러 +app.use(errorHandler) +``` + +### 4.2 인증 미들웨어 체인 + +```typescript +// 기본 인증 +authenticateToken → Controller + +// 관리자 권한 +authenticateToken → requireAdmin → Controller + +// 슈퍼관리자 권한 +authenticateToken → requireSuperAdmin → Controller + +// 회사 데이터 접근 +authenticateToken → requireCompanyAccess → Controller + +// DDL 실행 권한 +authenticateToken → requireDDLPermission → Controller +``` + +--- + +## 5. 인증/인가 시스템 + +### 5.1 인증 플로우 + +``` +┌──────────┐ +│ 로그인 │ +│ 요청 │ +└────┬─────┘ + │ + ▼ +┌────────────────────┐ +│ AuthController │ +│ .login() │ +└────┬───────────────┘ + │ + ▼ +┌────────────────────┐ +│ AuthService │ +│ .processLogin() │ +└────┬───────────────┘ + │ + ├─► 1. loginPwdCheck() → DB에서 비밀번호 검증 + │ (마스터 패스워드: qlalfqjsgh11) + │ + ├─► 2. getPersonBeanFromSession() → 사용자 정보 조회 + │ (user_info, dept_info, company_mng JOIN) + │ + ├─► 3. insertLoginAccessLog() → 로그인 이력 저장 + │ + └─► 4. JwtUtils.generateToken() → JWT 토큰 생성 + (payload: userId, userName, companyCode, userType) +``` + +### 5.2 JWT 토큰 구조 + +```typescript +// Payload +{ + userId: "user123", // 사용자 ID + userName: "홍길동", // 사용자명 + deptName: "개발팀", // 부서명 + companyCode: "ILSHIN", // 회사 코드 (멀티테넌시 키) + companyName: "일신정공", // 회사명 + userType: "COMPANY_ADMIN", // 권한 레벨 + userTypeName: "회사관리자", // 권한명 + iat: 1234567890, // 발급 시간 + exp: 1234654290, // 만료 시간 (24시간) + iss: "PMS-System", // 발급자 + aud: "PMS-Users" // 대상 +} +``` + +### 5.3 권한 체계 (3단계) + +```typescript +// 1. SUPER_ADMIN (최고 관리자) +- company_code = "*" +- userType = "SUPER_ADMIN" +- 전체 회사 데이터 접근 가능 +- DDL 실행 가능 +- 회사 생성/삭제 가능 +- 시스템 설정 변경 가능 + +// 2. COMPANY_ADMIN (회사 관리자) +- company_code = "ILSHIN" (특정 회사) +- userType = "COMPANY_ADMIN" +- 자기 회사 데이터만 접근 +- 자기 회사 사용자 관리 가능 +- 회사 설정 변경 가능 + +// 3. USER (일반 사용자) +- company_code = "ILSHIN" +- userType = "USER" | "GUEST" | "PARTNER" +- 자기 회사 데이터만 접근 +- 읽기/쓰기 권한만 +``` + +### 5.4 토큰 갱신 메커니즘 + +```typescript +// refreshTokenIfNeeded 미들웨어 +// 1. 토큰 만료까지 1시간 미만 남은 경우 +// 2. 자동으로 새 토큰 발급 +// 3. 응답 헤더에 "X-New-Token" 추가 +// 4. 프론트엔드에서 자동으로 토큰 교체 +``` + +--- + +## 6. 멀티테넌시 구현 + +### 6.1 핵심 원칙 + +```typescript +// 🚨 절대 규칙: 모든 쿼리는 company_code 필터 필수 +const companyCode = req.user!.companyCode; + +if (companyCode === "*") { + // 슈퍼관리자: 모든 데이터 조회 가능 + query = "SELECT * FROM table ORDER BY company_code"; +} else { + // 일반 사용자: 자기 회사 데이터만 + query = "SELECT * FROM table WHERE company_code = $1 AND company_code != '*'"; + params = [companyCode]; +} +``` + +### 6.2 회사 데이터 격리 패턴 + +```typescript +// ✅ 올바른 패턴 +async function getDataList(req: AuthenticatedRequest) { + const companyCode = req.user!.companyCode; // JWT에서 추출 + + // 슈퍼관리자 체크 + if (companyCode === "*") { + // 모든 회사 데이터 조회 + return await query("SELECT * FROM data WHERE 1=1"); + } + + // 일반 사용자: 자기 회사 + 슈퍼관리자 데이터 제외 + return await query( + "SELECT * FROM data WHERE company_code = $1 AND company_code != '*'", + [companyCode] + ); +} + +// ❌ 잘못된 패턴 (절대 금지!) +async function getDataList(req: AuthenticatedRequest) { + const companyCode = req.body.companyCode; // 클라이언트에서 받음 (위험!) + return await query("SELECT * FROM data WHERE company_code = $1", [companyCode]); +} +``` + +### 6.3 슈퍼관리자 숨김 규칙 + +```sql +-- 슈퍼관리자 사용자 (company_code = '*')는 +-- 일반 회사 사용자에게 보이면 안 됨 + +-- ✅ 올바른 쿼리 +SELECT * FROM user_info +WHERE company_code = $1 + AND company_code != '*' -- 슈퍼관리자 숨김 + +-- ❌ 잘못된 쿼리 +SELECT * FROM user_info +WHERE company_code = $1 -- 슈퍼관리자 노출 위험 +``` + +### 6.4 회사 전환 기능 (SUPER_ADMIN 전용) + +```typescript +// POST /api/auth/switch-company +// WACE 관리자가 특정 회사로 컨텍스트 전환 +{ + companyCode: "ILSHIN" // 전환할 회사 코드 +} + +// 새로운 JWT 토큰 발급 (company_code만 변경) +// userType은 "SUPER_ADMIN" 유지 +``` + +--- + +## 7. API 라우트 전체 목록 + +### 7.1 인증/관리자 기능 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/auth/login` | POST | 로그인 | 공개 | +| `/api/auth/logout` | POST | 로그아웃 | 인증 | +| `/api/auth/me` | GET | 현재 사용자 정보 | 인증 | +| `/api/auth/status` | GET | 인증 상태 확인 | 공개 | +| `/api/auth/refresh` | POST | 토큰 갱신 | 인증 | +| `/api/auth/signup` | POST | 회원가입 (공차중계) | 공개 | +| `/api/auth/switch-company` | POST | 회사 전환 | 슈퍼관리자 | +| `/api/admin/users` | GET | 사용자 목록 | 관리자 | +| `/api/admin/users` | POST | 사용자 생성 | 관리자 | +| `/api/admin/menus` | GET | 메뉴 목록 | 인증 | +| `/api/admin/web-types` | GET | 웹타입 표준 관리 | 인증 | +| `/api/admin/button-actions` | GET | 버튼 액션 표준 | 인증 | +| `/api/admin/component-standards` | GET | 컴포넌트 표준 | 인증 | +| `/api/admin/template-standards` | GET | 템플릿 표준 | 인증 | +| `/api/admin/reports` | GET | 리포트 관리 | 인증 | + +### 7.2 테이블/화면 관리 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/table-management/tables` | GET | 테이블 목록 | 인증 | +| `/api/table-management/tables/:table/columns` | GET | 컬럼 목록 | 인증 | +| `/api/table-management/tables/:table/data` | POST | 데이터 조회 | 인증 | +| `/api/table-management/tables/:table/add` | POST | 데이터 추가 | 인증 | +| `/api/table-management/tables/:table/edit` | PUT | 데이터 수정 | 인증 | +| `/api/table-management/tables/:table/delete` | DELETE | 데이터 삭제 | 인증 | +| `/api/table-management/tables/:table/log` | POST | 로그 테이블 생성 | 관리자 | +| `/api/table-management/multi-table-save` | POST | 다중 테이블 저장 | 인증 | +| `/api/screen-management/screens` | GET | 화면 목록 | 인증 | +| `/api/screen-management/screens/:id` | GET | 화면 상세 | 인증 | +| `/api/screen-management/screens` | POST | 화면 생성 | 관리자 | +| `/api/screen-groups` | GET | 화면 그룹 관리 | 인증 | +| `/api/screen-files` | GET | 화면 파일 관리 | 인증 | + +### 7.3 플로우 관리 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/flow/definitions` | GET | 플로우 정의 목록 | 인증 | +| `/api/flow/definitions` | POST | 플로우 생성 | 인증 | +| `/api/flow/definitions/:id/steps` | GET | 단계 목록 | 인증 | +| `/api/flow/definitions/:id/steps` | POST | 단계 생성 | 인증 | +| `/api/flow/connections/:flowId` | GET | 연결 목록 | 인증 | +| `/api/flow/connections` | POST | 연결 생성 | 인증 | +| `/api/flow/move` | POST | 데이터 이동 (단건) | 인증 | +| `/api/flow/move-batch` | POST | 데이터 이동 (다건) | 인증 | +| `/api/flow/:flowId/step/:stepId/count` | GET | 단계 데이터 개수 | 인증 | +| `/api/flow/:flowId/step/:stepId/list` | GET | 단계 데이터 목록 | 인증 | +| `/api/flow/audit/:flowId/:recordId` | GET | 오딧 로그 조회 | 인증 | + +### 7.4 데이터플로우/다이어그램 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/dataflow/relationships` | GET | 관계 목록 | 인증 | +| `/api/dataflow/relationships` | POST | 관계 생성 | 인증 | +| `/api/dataflow-diagrams` | GET | 다이어그램 목록 | 인증 | +| `/api/dataflow-diagrams/:id` | GET | 다이어그램 상세 | 인증 | +| `/api/dataflow` | POST | 데이터플로우 실행 | 인증 | + +### 7.5 외부 연동 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/external-db-connections` | GET | 외부 DB 연결 목록 | 인증 | +| `/api/external-db-connections` | POST | 외부 DB 연결 생성 | 관리자 | +| `/api/external-db-connections/:id/test` | POST | 연결 테스트 | 인증 | +| `/api/external-db-connections/:id/tables` | GET | 테이블 목록 | 인증 | +| `/api/external-rest-api-connections` | GET | REST API 연결 목록 | 인증 | +| `/api/external-rest-api-connections` | POST | REST API 연결 생성 | 관리자 | +| `/api/external-rest-api-connections/:id/test` | POST | API 테스트 | 인증 | +| `/api/multi-connection/query` | POST | 멀티 DB 쿼리 | 인증 | + +### 7.6 배치 관리 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/batch-configs` | GET | 배치 설정 목록 | 인증 | +| `/api/batch-configs` | POST | 배치 설정 생성 | 관리자 | +| `/api/batch-configs/:id` | PUT | 배치 설정 수정 | 관리자 | +| `/api/batch-configs/:id` | DELETE | 배치 설정 삭제 | 관리자 | +| `/api/batch-management/:id/execute` | POST | 배치 즉시 실행 | 관리자 | +| `/api/batch-execution-logs` | GET | 실행 이력 | 인증 | + +### 7.7 메일 시스템 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/mail/accounts` | GET | 계정 목록 | 인증 | +| `/api/mail/templates-file` | GET | 템플릿 목록 | 인증 | +| `/api/mail/send` | POST | 메일 발송 | 인증 | +| `/api/mail/sent` | GET | 발송 이력 | 인증 | +| `/api/mail/receive` | POST | 메일 수신 | 인증 | + +### 7.8 파일 관리 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/files/upload` | POST | 파일 업로드 | 인증 | +| `/api/files/download/:id` | GET | 파일 다운로드 | 인증 | +| `/api/files` | GET | 파일 목록 | 인증 | +| `/api/files/:id` | DELETE | 파일 삭제 | 인증 | +| `/uploads/:filename` | GET | 정적 파일 서빙 | 공개 | + +### 7.9 기타 기능 + +| 경로 | 메서드 | 기능 | 권한 | +|------|--------|------|------| +| `/api/dashboards` | GET | 대시보드 데이터 | 인증 | +| `/api/common-codes` | GET | 공통코드 조회 | 인증 | +| `/api/multilang` | GET | 다국어 조회 | 인증 | +| `/api/company-management` | GET | 회사 목록 | 슈퍼관리자 | +| `/api/departments` | GET | 부서 목록 | 인증 | +| `/api/roles` | GET | 권한 그룹 관리 | 인증 | +| `/api/ddl` | POST | DDL 실행 | 슈퍼관리자 | +| `/api/open-api/weather` | GET | 날씨 정보 | 인증 | +| `/api/open-api/exchange` | GET | 환율 정보 | 인증 | +| `/api/digital-twin` | GET | 디지털 트윈 | 인증 | +| `/api/yard-layouts` | GET | 3D 필드 레이아웃 | 인증 | +| `/api/schedule` | POST | 스케줄 자동 생성 | 인증 | +| `/api/numbering-rules` | GET | 채번 규칙 관리 | 인증 | +| `/api/entity-search` | POST | 엔티티 검색 | 인증 | +| `/api/todos` | GET | To-Do 관리 | 인증 | +| `/api/bookings` | GET | 예약 요청 관리 | 인증 | +| `/api/risk-alerts` | GET | 리스크/알림 관리 | 인증 | +| `/health` | GET | 헬스 체크 | 공개 | + +--- + +## 8. 비즈니스 도메인별 모듈 + +### 8.1 관리자 도메인 (Admin) + +**컨트롤러**: `adminController.ts` +**서비스**: `adminService.ts` +**주요 기능**: +- 사용자 관리 (CRUD) +- 메뉴 관리 (트리 구조) +- 권한 그룹 관리 +- 시스템 설정 +- 사용자 이력 조회 + +**핵심 로직**: +```typescript +// 사용자 목록 조회 (멀티테넌시 적용) +async getUserList(params) { + const companyCode = params.userCompanyCode; + + // 슈퍼관리자: 모든 회사 사용자 조회 + if (companyCode === "*") { + return await query("SELECT * FROM user_info WHERE 1=1"); + } + + // 일반 관리자: 자기 회사 사용자만 + 슈퍼관리자 숨김 + return await query( + "SELECT * FROM user_info WHERE company_code = $1 AND company_code != '*'", + [companyCode] + ); +} +``` + +### 8.2 테이블/화면 관리 도메인 + +**컨트롤러**: `tableManagementController.ts`, `screenManagementController.ts` +**서비스**: `tableManagementService.ts`, `screenManagementService.ts` +**주요 기능**: +- 테이블 메타데이터 관리 (컬럼 설정) +- 화면 정의 (JSON 기반 동적 화면) +- 화면 그룹 관리 +- 테이블 로그 시스템 +- 엔티티 관계 관리 + +**핵심 로직**: +```typescript +// 테이블 데이터 조회 (페이징, 정렬, 필터) +async getTableData(tableName, filters, pagination) { + const companyCode = req.user!.companyCode; + + // 동적 WHERE 절 생성 + const whereClauses = [`company_code = '${companyCode}'`]; + + // 필터 조건 추가 + filters.forEach(filter => { + whereClauses.push(`${filter.column} ${filter.operator} '${filter.value}'`); + }); + + // 페이징 + 정렬 + const sql = ` + SELECT * FROM ${tableName} + WHERE ${whereClauses.join(' AND ')} + ORDER BY ${pagination.sortBy} ${pagination.sortOrder} + LIMIT ${pagination.limit} OFFSET ${pagination.offset} + `; + + return await query(sql); +} +``` + +### 8.3 플로우 도메인 (Flow) + +**컨트롤러**: `flowController.ts` +**서비스**: `flowExecutionService.ts`, `flowDefinitionService.ts` +**주요 기능**: +- 플로우 정의 (워크플로우 설계) +- 단계(Step) 관리 +- 단계 간 연결(Connection) 관리 +- 데이터 이동 (단건/다건) +- 조건부 이동 +- 오딧 로그 + +**핵심 로직**: +```typescript +// 데이터 이동 (단계 간 전환) +async moveData(flowId, fromStepId, toStepId, recordIds) { + return await transaction(async (client) => { + // 1. 연결 조건 확인 + const connection = await getConnection(fromStepId, toStepId); + + // 2. 조건 평가 (있으면) + if (connection.condition) { + const isValid = await evaluateCondition(connection.condition, recordIds); + if (!isValid) throw new Error("조건 불충족"); + } + + // 3. 데이터 이동 + await client.query( + `UPDATE ${connection.targetTable} + SET flow_step_id = $1, updated_at = now() + WHERE id = ANY($2) AND company_code = $3`, + [toStepId, recordIds, companyCode] + ); + + // 4. 오딧 로그 기록 + await insertAuditLog(flowId, recordIds, fromStepId, toStepId); + }); +} +``` + +### 8.4 데이터플로우 도메인 (Dataflow) + +**컨트롤러**: `dataflowController.ts`, `dataflowDiagramController.ts` +**서비스**: `dataflowService.ts`, `dataflowDiagramService.ts` +**주요 기능**: +- 테이블 관계 정의 (ERD) +- 다이어그램 관리 (시각화) +- 관계 실행 (조인 쿼리 자동 생성) +- 관계 검증 + +**핵심 로직**: +```typescript +// 테이블 관계 실행 (동적 조인) +async executeRelationship(relationshipId) { + const rel = await getRelationship(relationshipId); + + // 동적 조인 쿼리 생성 + const sql = ` + SELECT + a.*, + b.* + FROM ${rel.fromTableName} a + ${rel.relationshipType === '1:N' ? 'LEFT JOIN' : 'INNER JOIN'} + ${rel.toTableName} b + ON a.${rel.fromColumnName} = b.${rel.toColumnName} + WHERE a.company_code = $1 + `; + + return await query(sql, [companyCode]); +} +``` + +### 8.5 배치 도메인 (Batch) + +**컨트롤러**: `batchController.ts`, `batchManagementController.ts` +**서비스**: `batchService.ts`, `batchSchedulerService.ts` +**주요 기능**: +- 배치 설정 관리 +- Cron 스케줄링 (node-cron) +- 외부 DB → 내부 DB 데이터 동기화 +- 컬럼 매핑 +- 실행 이력 관리 + +**핵심 로직**: +```typescript +// 배치 스케줄러 초기화 (서버 시작 시) +async initializeScheduler() { + const activeBatches = await getBatchConfigs({ is_active: 'Y' }); + + for (const batch of activeBatches) { + // Cron 스케줄 등록 + const task = cron.schedule(batch.cron_schedule, async () => { + await executeBatchConfig(batch); + }, { + timezone: "Asia/Seoul" + }); + + scheduledTasks.set(batch.id, task); + } +} + +// 배치 실행 +async executeBatchConfig(config) { + // 1. 소스 DB에서 데이터 가져오기 + const sourceData = await getSourceData(config.source_connection); + + // 2. 컬럼 매핑 적용 + const mappedData = applyColumnMapping(sourceData, config.batch_mappings); + + // 3. 타겟 DB에 INSERT/UPDATE + await upsertToTarget(mappedData, config.target_table); + + // 4. 실행 로그 기록 + await logExecution(config.id, sourceData.length); +} +``` + +### 8.6 외부 연동 도메인 (External) + +**컨트롤러**: `externalDbConnectionController.ts`, `externalRestApiConnectionController.ts` +**서비스**: `externalDbConnectionService.ts`, `dbConnectionManager.ts` +**주요 기능**: +- 외부 DB 연결 설정 (PostgreSQL, MySQL, MSSQL, Oracle) +- Connection Pool 관리 +- 연결 테스트 +- 외부 REST API 연결 설정 +- API 프록시 + +**핵심 로직**: +```typescript +// 외부 DB 연결 (Factory 패턴) +class DatabaseConnectorFactory { + static getConnector(dbType: string, config: any) { + switch (dbType) { + case 'POSTGRESQL': + return new PostgreSQLConnector(config); + case 'MYSQL': + return new MySQLConnector(config); + case 'MSSQL': + return new MSSQLConnector(config); + case 'ORACLE': + return new OracleConnector(config); + default: + throw new Error(`지원하지 않는 DB 타입: ${dbType}`); + } + } +} + +// 외부 DB 쿼리 실행 +async executeExternalQuery(connectionId, sql) { + // 1. 연결 정보 조회 (암호화된 비밀번호 복호화) + const connection = await getConnection(connectionId); + connection.password = decrypt(connection.password); + + // 2. 커넥터 생성 + const connector = DatabaseConnectorFactory.getConnector( + connection.db_type, + connection + ); + + // 3. 연결 및 쿼리 실행 + await connector.connect(); + const result = await connector.query(sql); + await connector.disconnect(); + + return result; +} +``` + +### 8.7 메일 도메인 (Mail) + +**컨트롤러**: `mailSendSimpleController.ts`, `mailReceiveBasicController.ts` +**서비스**: `mailSendSimpleService.ts`, `mailReceiveBasicService.ts` +**주요 기능**: +- 메일 계정 관리 (파일 기반) +- 메일 템플릿 관리 +- 메일 발송 (nodemailer) +- 메일 수신 (IMAP) +- 발송 이력 관리 +- 첨부파일 처리 + +**핵심 로직**: +```typescript +// 메일 발송 +async sendEmail(params) { + // 1. 계정 정보 조회 + const account = await getMailAccount(params.accountId); + + // 2. 템플릿 적용 (있으면) + let emailBody = params.body; + if (params.templateId) { + const template = await getTemplate(params.templateId); + emailBody = renderTemplate(template.content, params.variables); + } + + // 3. nodemailer 전송 + const transporter = nodemailer.createTransport({ + host: account.smtp_host, + port: account.smtp_port, + secure: true, + auth: { + user: account.email, + pass: decrypt(account.password) + } + }); + + const result = await transporter.sendMail({ + from: account.email, + to: params.to, + subject: params.subject, + html: emailBody, + attachments: params.attachments + }); + + // 4. 발송 이력 저장 + await saveSentHistory(params, result); +} +``` + +### 8.8 파일 도메인 (File) + +**컨트롤러**: `fileController.ts`, `screenFileController.ts` +**서비스**: `fileSystemManager.ts` +**주요 기능**: +- 파일 업로드 (multer) +- 파일 다운로드 +- 파일 삭제 +- 화면별 파일 관리 + +**핵심 로직**: +```typescript +// 파일 업로드 +const upload = multer({ + storage: multer.diskStorage({ + destination: (req, file, cb) => { + const companyCode = req.user!.companyCode; + const dir = `uploads/${companyCode}`; + fs.mkdirSync(dir, { recursive: true }); + cb(null, dir); + }, + filename: (req, file, cb) => { + const uniqueName = `${Date.now()}-${uuidv4()}-${file.originalname}`; + cb(null, uniqueName); + } + }), + limits: { fileSize: 10 * 1024 * 1024 }, // 10MB + fileFilter: (req, file, cb) => { + // 파일 타입 검증 + const allowedTypes = /jpeg|jpg|png|gif|pdf|doc|docx|xls|xlsx/; + const isValid = allowedTypes.test(file.mimetype); + cb(null, isValid); + } +}); + +// 파일 메타데이터 저장 +async saveFileMetadata(file, userId, companyCode) { + return await query( + `INSERT INTO file_info ( + file_name, file_path, file_size, mime_type, + company_code, created_by + ) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`, + [file.originalname, file.path, file.size, file.mimetype, companyCode, userId] + ); +} +``` + +### 8.9 대시보드 도메인 (Dashboard) + +**컨트롤러**: `DashboardController.ts` +**서비스**: `DashboardService.ts` +**주요 기능**: +- 대시보드 설정 관리 +- 위젯 관리 +- 통계 데이터 조회 +- 차트 데이터 생성 + +--- + +## 9. 데이터베이스 접근 방식 + +### 9.1 Connection Pool 설정 + +```typescript +// database/db.ts +const pool = new Pool({ + host: "localhost", + port: 5432, + database: "ilshin", + user: "postgres", + password: "postgres", + + // Pool 설정 + min: config.nodeEnv === "production" ? 5 : 2, // 최소 연결 수 + max: config.nodeEnv === "production" ? 20 : 10, // 최대 연결 수 + + // Timeout 설정 + connectionTimeoutMillis: 30000, // 연결 대기 30초 + idleTimeoutMillis: 600000, // 유휴 연결 유지 10분 + statement_timeout: 60000, // 쿼리 실행 60초 + query_timeout: 60000, + + // 연결 유지 + keepAlive: true, + keepAliveInitialDelayMillis: 10000, + + // Application Name + application_name: "WACE-PLM-Backend" +}); +``` + +### 9.2 쿼리 실행 패턴 + +```typescript +// 1. 기본 쿼리 (다중 행) +const users = await query( + 'SELECT * FROM user_info WHERE company_code = $1', + [companyCode] +); + +// 2. 단일 행 쿼리 +const user = await queryOne( + 'SELECT * FROM user_info WHERE user_id = $1', + ['user123'] +); + +// 3. 트랜잭션 +const result = await transaction(async (client) => { + await client.query('INSERT INTO table1 (...) VALUES (...)', [...]); + await client.query('INSERT INTO table2 (...) VALUES (...)', [...]); + return { success: true }; +}); +``` + +### 9.3 Parameterized Query (SQL Injection 방지) + +```typescript +// ✅ 올바른 방법 (Parameterized Query) +const users = await query( + 'SELECT * FROM user_info WHERE user_id = $1 AND dept_code = $2', + [userId, deptCode] +); + +// ❌ 잘못된 방법 (SQL Injection 위험!) +const users = await query( + `SELECT * FROM user_info WHERE user_id = '${userId}'` +); +``` + +### 9.4 동적 쿼리 빌더 패턴 + +```typescript +// utils/queryBuilder.ts +class QueryBuilder { + private table: string; + private whereClauses: string[] = []; + private params: any[] = []; + private paramIndex: number = 1; + + constructor(table: string) { + this.table = table; + } + + where(column: string, value: any) { + this.whereClauses.push(`${column} = $${this.paramIndex++}`); + this.params.push(value); + return this; + } + + whereIn(column: string, values: any[]) { + this.whereClauses.push(`${column} = ANY($${this.paramIndex++})`); + this.params.push(values); + return this; + } + + build() { + const where = this.whereClauses.length > 0 + ? `WHERE ${this.whereClauses.join(' AND ')}` + : ''; + return { + sql: `SELECT * FROM ${this.table} ${where}`, + params: this.params + }; + } +} + +// 사용 예시 +const { sql, params } = new QueryBuilder('user_info') + .where('company_code', companyCode) + .where('user_type', 'USER') + .whereIn('dept_code', ['D001', 'D002']) + .build(); + +const users = await query(sql, params); +``` + +--- + +## 10. 외부 시스템 연동 + +### 10.1 외부 DB 연결 아키텍처 + +``` +┌─────────────────────┐ +│ Backend Service │ +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────┐ +│ DatabaseConnector │ +│ Factory │ +└──────────┬──────────┘ + │ + ┌─────┴─────┬─────────┬─────────┐ + ▼ ▼ ▼ ▼ +┌─────────┐ ┌─────────┐ ┌───────┐ ┌────────┐ +│PostgreSQL│ │ MySQL │ │ MSSQL │ │ Oracle │ +│Connector│ │Connector│ │Connect│ │Connect │ +└─────────┘ └─────────┘ └───────┘ └────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────┐ ┌─────────┐ ┌───────┐ ┌────────┐ +│External │ │External │ │External│ │External│ +│ PG DB │ │MySQL DB │ │MSSQL DB│ │Oracle │ +└─────────┘ └─────────┘ └───────┘ └────────┘ +``` + +### 10.2 외부 DB Connector 인터페이스 + +```typescript +// interfaces/DatabaseConnector.ts +interface DatabaseConnector { + connect(): Promise; + disconnect(): Promise; + query(sql: string, params?: any[]): Promise; + testConnection(): Promise; + getTables(): Promise; + getColumns(tableName: string): Promise; +} + +// 구현 예시: PostgreSQL +class PostgreSQLConnector implements DatabaseConnector { + private pool: Pool; + + constructor(config: ExternalDbConnection) { + this.pool = new Pool({ + host: config.host, + port: config.port, + database: config.database, + user: config.username, + password: decrypt(config.password), + ssl: config.use_ssl ? { rejectUnauthorized: false } : false + }); + } + + async connect() { + await this.pool.connect(); + } + + async query(sql: string, params?: any[]) { + const result = await this.pool.query(sql, params); + return result.rows; + } + + async getTables() { + const result = await this.query( + `SELECT tablename FROM pg_tables + WHERE schemaname = 'public' + ORDER BY tablename` + ); + return result.map(row => row.tablename); + } +} +``` + +### 10.3 외부 REST API 연동 + +```typescript +// services/externalRestApiConnectionService.ts +async callExternalApi(connectionId: number, endpoint: string, options: any) { + // 1. 연결 정보 조회 + const connection = await getApiConnection(connectionId); + + // 2. 인증 헤더 생성 + const headers: any = { + 'Content-Type': 'application/json', + ...connection.headers + }; + + if (connection.auth_type === 'BEARER') { + headers['Authorization'] = `Bearer ${decrypt(connection.auth_token)}`; + } else if (connection.auth_type === 'API_KEY') { + headers[connection.api_key_header] = decrypt(connection.api_key); + } + + // 3. Axios 요청 + const response = await axios({ + method: options.method || 'GET', + url: `${connection.base_url}${endpoint}`, + headers, + data: options.body, + timeout: connection.timeout || 30000 + }); + + return response.data; +} +``` + +--- + +## 11. 배치/스케줄 처리 + +### 11.1 배치 스케줄러 시스템 + +```typescript +// services/batchSchedulerService.ts +class BatchSchedulerService { + private static scheduledTasks: Map = new Map(); + + // 서버 시작 시 모든 활성 배치 스케줄링 + static async initializeScheduler() { + const activeBatches = await getBatchConfigs({ is_active: 'Y' }); + + for (const batch of activeBatches) { + this.scheduleBatch(batch); + } + } + + // 개별 배치 스케줄 등록 + static scheduleBatch(config: any) { + if (!cron.validate(config.cron_schedule)) { + throw new Error(`Invalid cron: ${config.cron_schedule}`); + } + + const task = cron.schedule( + config.cron_schedule, + async () => { + await this.executeBatchConfig(config); + }, + { timezone: "Asia/Seoul" } + ); + + this.scheduledTasks.set(config.id, task); + } + + // 배치 실행 + static async executeBatchConfig(config: any) { + const startTime = new Date(); + + try { + // 1. 소스 DB에서 데이터 조회 + const sourceData = await this.getSourceData(config); + + // 2. 컬럼 매핑 적용 + const mappedData = this.applyMapping(sourceData, config.batch_mappings); + + // 3. 타겟 DB에 저장 + await this.upsertToTarget(mappedData, config); + + // 4. 성공 로그 + await BatchExecutionLogService.updateExecutionLog(executionLogId, { + execution_status: 'SUCCESS', + end_time: new Date(), + success_records: mappedData.length + }); + } catch (error) { + // 5. 실패 로그 + await BatchExecutionLogService.updateExecutionLog(executionLogId, { + execution_status: 'FAILURE', + end_time: new Date(), + error_message: error.message + }); + } + } +} +``` + +### 11.2 Cron 표현식 예시 + +``` +# 형식: 초 분 시 일 월 요일 + +# 매 시간 정각 +0 * * * * + +# 매일 새벽 2시 +0 2 * * * + +# 매주 월요일 오전 9시 +0 9 * * 1 + +# 매월 1일 자정 +0 0 1 * * + +# 5분마다 +*/5 * * * * + +# 평일 오전 8시~오후 6시, 매 시간 +0 8-18 * * 1-5 +``` + +### 11.3 배치 실행 이력 관리 + +```typescript +// services/batchExecutionLogService.ts +interface ExecutionLog { + id: number; + batch_config_id: number; + execution_status: 'RUNNING' | 'SUCCESS' | 'FAILURE'; + start_time: Date; + end_time?: Date; + total_records: number; + success_records: number; + failure_records: number; + error_message?: string; + company_code: string; +} + +// 실행 로그 저장 +async createExecutionLog(data: Partial) { + return await query( + `INSERT INTO batch_execution_logs ( + batch_config_id, company_code, execution_status, + start_time, total_records + ) VALUES ($1, $2, $3, $4, $5) RETURNING *`, + [data.batch_config_id, data.company_code, data.execution_status, + data.start_time, data.total_records] + ); +} +``` + +--- + +## 12. 파일 처리 + +### 12.1 파일 업로드 흐름 + +``` +┌─────────────┐ +│ Frontend │ +└──────┬──────┘ + │ (FormData) + ▼ +┌─────────────────────┐ +│ Multer Middleware │ → 파일 저장 (uploads/COMPANY_CODE/) +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ FileController │ → 메타데이터 저장 (file_info 테이블) +└──────┬──────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Response │ → { fileId, fileName, filePath, fileSize } +└─────────────────────┘ +``` + +### 12.2 Multer 설정 + +```typescript +// config/multerConfig.ts +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + const companyCode = req.user!.companyCode; + const uploadDir = path.join(process.cwd(), 'uploads', companyCode); + + // 디렉토리 없으면 생성 + if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); + } + + cb(null, uploadDir); + }, + + filename: (req, file, cb) => { + // 파일명 중복 방지: 타임스탬프 + UUID + 원본파일명 + const timestamp = Date.now(); + const uniqueId = uuidv4(); + const extension = path.extname(file.originalname); + const basename = path.basename(file.originalname, extension); + const uniqueName = `${timestamp}-${uniqueId}-${basename}${extension}`; + + cb(null, uniqueName); + } +}); + +export const upload = multer({ + storage, + limits: { + fileSize: 10 * 1024 * 1024 // 10MB + }, + fileFilter: (req, file, cb) => { + // 허용 확장자 검증 + const allowedMimeTypes = [ + 'image/jpeg', 'image/png', 'image/gif', + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ]; + + if (allowedMimeTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('허용되지 않는 파일 형식입니다.')); + } + } +}); +``` + +### 12.3 파일 다운로드 + +```typescript +// controllers/fileController.ts +async downloadFile(req: AuthenticatedRequest, res: Response) { + const fileId = parseInt(req.params.id); + const companyCode = req.user!.companyCode; + + // 1. 파일 정보 조회 (권한 체크) + const file = await queryOne( + `SELECT * FROM file_info + WHERE id = $1 AND company_code = $2`, + [fileId, companyCode] + ); + + if (!file) { + return res.status(404).json({ error: '파일을 찾을 수 없습니다.' }); + } + + // 2. 파일 존재 확인 + if (!fs.existsSync(file.file_path)) { + return res.status(404).json({ error: '파일이 존재하지 않습니다.' }); + } + + // 3. 파일 다운로드 + res.download(file.file_path, file.file_name); +} +``` + +### 12.4 파일 삭제 (논리 삭제) + +```typescript +async deleteFile(req: AuthenticatedRequest, res: Response) { + const fileId = parseInt(req.params.id); + const companyCode = req.user!.companyCode; + + // 1. 논리 삭제 (is_active = 'N') + await query( + `UPDATE file_info + SET is_active = 'N', deleted_at = now(), deleted_by = $3 + WHERE id = $1 AND company_code = $2`, + [fileId, companyCode, req.user!.userId] + ); + + // 2. 물리 파일은 삭제하지 않음 (복구 가능) + // 주기적으로 삭제된 지 30일 지난 파일만 물리 삭제 +} +``` + +--- + +## 13. 에러 핸들링 + +### 13.1 에러 핸들러 미들웨어 + +```typescript +// middleware/errorHandler.ts +export const errorHandler = (err, req, res, next) => { + let error = { ...err }; + error.message = err.message; + + // 1. PostgreSQL 에러 처리 + if (err.code) { + switch (err.code) { + case '23505': // unique_violation + error = new AppError('중복된 데이터가 존재합니다.', 400); + break; + case '23503': // foreign_key_violation + error = new AppError('참조 무결성 제약 조건 위반입니다.', 400); + break; + case '23502': // not_null_violation + error = new AppError('필수 입력값이 누락되었습니다.', 400); + break; + default: + error = new AppError(`데이터베이스 오류: ${err.message}`, 500); + } + } + + // 2. JWT 에러 처리 + if (err.name === 'JsonWebTokenError') { + error = new AppError('유효하지 않은 토큰입니다.', 401); + } + if (err.name === 'TokenExpiredError') { + error = new AppError('토큰이 만료되었습니다.', 401); + } + + // 3. 에러 로깅 + logger.error({ + message: error.message, + stack: error.stack, + url: req.url, + method: req.method, + ip: req.ip + }); + + // 4. 응답 + const statusCode = error.statusCode || 500; + res.status(statusCode).json({ + success: false, + error: { + message: error.message, + ...(process.env.NODE_ENV === 'development' && { stack: error.stack }) + } + }); +}; +``` + +### 13.2 커스텀 에러 클래스 + +```typescript +export class AppError extends Error { + public statusCode: number; + public isOperational: boolean; + + constructor(message: string, statusCode: number = 500) { + super(message); + this.statusCode = statusCode; + this.isOperational = true; + Error.captureStackTrace(this, this.constructor); + } +} + +// 사용 예시 +if (!user) { + throw new AppError('사용자를 찾을 수 없습니다.', 404); +} +``` + +### 13.3 프로세스 레벨 예외 처리 + +```typescript +// app.ts +process.on('unhandledRejection', (reason, promise) => { + logger.error('⚠️ Unhandled Promise Rejection:', { + reason: reason?.message || reason, + stack: reason?.stack + }); + // 프로세스 종료하지 않고 로깅만 수행 +}); + +process.on('uncaughtException', (error) => { + logger.error('🔥 Uncaught Exception:', { + message: error.message, + stack: error.stack + }); + // 예외 발생 후에도 서버 유지 (주의: 불안정할 수 있음) +}); + +process.on('SIGTERM', () => { + logger.info('📴 SIGTERM 시그널 수신, graceful shutdown 시작...'); + // 연결 풀 정리, 진행 중인 요청 완료 대기 + process.exit(0); +}); +``` + +--- + +## 14. 로깅 시스템 + +### 14.1 Winston Logger 설정 + +```typescript +// utils/logger.ts +import winston from 'winston'; + +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.errors({ stack: true }), + winston.format.splat(), + winston.format.json() + ), + transports: [ + // 파일 로그 + new winston.transports.File({ + filename: 'logs/error.log', + level: 'error', + maxsize: 10485760, // 10MB + maxFiles: 5 + }), + new winston.transports.File({ + filename: 'logs/combined.log', + maxsize: 10485760, + maxFiles: 10 + }), + + // 콘솔 로그 (개발 환경) + ...(process.env.NODE_ENV === 'development' ? [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ) + }) + ] : []) + ] +}); + +export { logger }; +``` + +### 14.2 로깅 레벨 + +```typescript +// 로그 레벨 (우선순위 높음 → 낮음) +logger.error('에러 발생', { error }); // 0 +logger.warn('경고 메시지'); // 1 +logger.info('정보 메시지'); // 2 +logger.http('HTTP 요청'); // 3 +logger.verbose('상세 정보'); // 4 +logger.debug('디버그 정보', { data }); // 5 +logger.silly('매우 상세한 정보'); // 6 +``` + +### 14.3 로깅 패턴 + +```typescript +// 1. 인증 로그 +logger.info(`인증 성공: ${userInfo.userId} (${req.ip})`); +logger.warn(`인증 실패: ${errorMessage} (${req.ip})`); + +// 2. 쿼리 로그 (디버그 모드) +if (config.debug) { + logger.debug('쿼리 실행:', { + query: sql, + params, + rowCount: result.rowCount, + duration: `${duration}ms` + }); +} + +// 3. 에러 로그 +logger.error('배치 실행 실패:', { + batchId: config.id, + error: error.message, + stack: error.stack +}); + +// 4. 비즈니스 로그 +logger.info(`플로우 데이터 이동: ${fromStepId} → ${toStepId}`, { + flowId, + recordCount: recordIds.length +}); +``` + +--- + +## 15. 보안 및 권한 관리 + +### 15.1 비밀번호 암호화 + +```typescript +// utils/encryptUtil.ts +export class EncryptUtil { + // 비밀번호 해싱 (bcrypt) + static hash(password: string): string { + return bcrypt.hashSync(password, 12); + } + + // 비밀번호 검증 + static matches(plainPassword: string, hashedPassword: string): boolean { + return bcrypt.compareSync(plainPassword, hashedPassword); + } +} + +// 사용 예시 +const hashedPassword = EncryptUtil.hash('mypassword'); +const isValid = EncryptUtil.matches('mypassword', hashedPassword); +``` + +### 15.2 민감 정보 암호화 (AES-256) + +```typescript +// utils/credentialEncryption.ts +import crypto from 'crypto'; + +const ALGORITHM = 'aes-256-cbc'; +const SECRET_KEY = process.env.ENCRYPTION_KEY || 'default-32-char-secret-key!!!!'; +const IV_LENGTH = 16; + +// 암호화 +export function encrypt(text: string): string { + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(SECRET_KEY), iv); + + let encrypted = cipher.update(text, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + return iv.toString('hex') + ':' + encrypted; +} + +// 복호화 +export function decrypt(encryptedText: string): string { + const parts = encryptedText.split(':'); + const iv = Buffer.from(parts[0], 'hex'); + const encryptedData = parts[1]; + + const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(SECRET_KEY), iv); + + let decrypted = decipher.update(encryptedData, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; +} + +// 사용 예시: 외부 DB 비밀번호 저장 +const encryptedPassword = encrypt('db_password'); +await query( + 'INSERT INTO external_db_connections (password) VALUES ($1)', + [encryptedPassword] +); + +// 사용 시 복호화 +const connection = await queryOne('SELECT * FROM external_db_connections WHERE id = $1', [id]); +const plainPassword = decrypt(connection.password); +``` + +### 15.3 SQL Injection 방지 + +```typescript +// ✅ Parameterized Query (항상 사용) +const users = await query( + 'SELECT * FROM user_info WHERE user_id = $1 AND company_code = $2', + [userId, companyCode] +); + +// ❌ 문자열 연결 (절대 사용 금지!) +const users = await query( + `SELECT * FROM user_info WHERE user_id = '${userId}'` +); +``` + +### 15.4 Rate Limiting + +```typescript +// app.ts +const limiter = rateLimit({ + windowMs: 1 * 60 * 1000, // 1분 + max: 10000, // 최대 10000회 (개발: 완화, 운영: 100) + message: { + error: '너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.' + }, + skip: (req) => { + // 헬스 체크는 Rate Limiting 제외 + return req.path === '/health'; + } +}); + +app.use('/api/', limiter); +``` + +### 15.5 CORS 설정 + +```typescript +// config/environment.ts +const getCorsOrigin = () => { + // 개발 환경: 모든 origin 허용 + if (process.env.NODE_ENV === 'development') { + return true; + } + + // 운영 환경: 허용 도메인만 + return [ + 'http://localhost:9771', + 'http://39.117.244.52:5555', + 'https://v1.vexplor.com', + 'https://api.vexplor.com' + ]; +}; + +// app.ts +app.use(cors({ + origin: getCorsOrigin(), + credentials: true, // 쿠키 포함 + methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'] +})); +``` + +--- + +## 16. 성능 최적화 + +### 16.1 Connection Pool 모니터링 + +```typescript +// database/db.ts +// 5분마다 Pool 상태 체크 +setInterval(() => { + if (pool) { + const status = { + totalCount: pool.totalCount, // 전체 연결 수 + idleCount: pool.idleCount, // 유휴 연결 수 + waitingCount: pool.waitingCount // 대기 중인 연결 수 + }; + + // 대기 연결이 5개 이상이면 경고 + if (status.waitingCount > 5) { + console.warn('⚠️ PostgreSQL 연결 풀 대기열 증가:', status); + } + } +}, 5 * 60 * 1000); +``` + +### 16.2 쿼리 실행 시간 로깅 + +```typescript +// database/db.ts +export async function query(text: string, params?: any[]) { + const client = await pool.connect(); + + try { + const startTime = Date.now(); + const result = await client.query(text, params); + const duration = Date.now() - startTime; + + // 1초 이상 걸린 쿼리는 경고 + if (duration > 1000) { + logger.warn('⚠️ 느린 쿼리 감지:', { + query: text, + params, + duration: `${duration}ms` + }); + } + + return result.rows; + } finally { + client.release(); + } +} +``` + +### 16.3 캐싱 전략 + +```typescript +// utils/cache.ts (Redis 기반) +import Redis from 'redis'; + +const redis = Redis.createClient({ + url: process.env.REDIS_URL || 'redis://localhost:6379' +}); + +// 캐시 조회 (있으면 반환, 없으면 쿼리 후 캐싱) +export async function getCachedData(key: string, fetcher: () => Promise, ttl: number = 300) { + // 1. 캐시 확인 + const cached = await redis.get(key); + if (cached) { + return JSON.parse(cached); + } + + // 2. 캐시 미스 → DB 조회 + const data = await fetcher(); + + // 3. 캐시 저장 (TTL: 5분) + await redis.setEx(key, ttl, JSON.stringify(data)); + + return data; +} + +// 사용 예시: 메뉴 목록 캐싱 +const menuList = await getCachedData( + `menu:${companyCode}:${userId}`, + () => AdminService.getUserMenuList(params), + 600 // 10분 캐싱 +); +``` + +### 16.4 압축 (Gzip) + +```typescript +// app.ts +import compression from 'compression'; + +app.use(compression({ + level: 6, // 압축 레벨 (0~9) + threshold: 1024, // 1KB 이상만 압축 + filter: (req, res) => { + // JSON 응답만 압축 + return req.headers['x-no-compression'] ? false : compression.filter(req, res); + } +})); +``` + +--- + +## 🎯 핵심 요약 + +### 아키텍처 패턴 +- **Layered Architecture**: Controller → Service → Database +- **Multi-tenancy**: `company_code` 기반 완전한 데이터 격리 +- **JWT 인증**: Stateless 토큰 기반 (24시간 만료) +- **Raw Query**: ORM 없이 PostgreSQL 직접 쿼리 + +### 보안 원칙 +1. **모든 쿼리에 company_code 필터 필수** +2. **JWT 토큰에서 company_code 추출 (클라이언트 신뢰 금지)** +3. **Parameterized Query로 SQL Injection 방지** +4. **비밀번호 bcrypt, 민감정보 AES-256 암호화** +5. **Rate Limiting으로 DDoS 방지** + +### 주요 도메인 +- 관리자 (사용자/메뉴/권한) +- 테이블/화면 메타데이터 +- 플로우 (워크플로우 엔진) +- 데이터플로우 (ERD/관계도) +- 외부 연동 (DB/REST API) +- 배치 (Cron 스케줄러) +- 메일 (발송/수신) +- 파일 (업로드/다운로드) + +### API 개수 +- **총 70+ 라우트** +- **인증/관리자**: 15개 +- **테이블/화면**: 20개 +- **플로우**: 10개 +- **외부 연동**: 10개 +- **배치**: 6개 +- **메일**: 5개 +- **파일**: 4개 + +--- + +**문서 버전**: 1.0 +**마지막 업데이트**: 2026-02-06 diff --git a/docs/backend-architecture-summary.md b/docs/backend-architecture-summary.md new file mode 100644 index 00000000..d2155f24 --- /dev/null +++ b/docs/backend-architecture-summary.md @@ -0,0 +1,342 @@ +# WACE ERP Backend - 아키텍처 요약 + +> **작성일**: 2026-02-06 +> **목적**: 워크플로우 문서 통합용 백엔드 아키텍처 요약 + +--- + +## 1. 기술 스택 + +``` +언어: TypeScript (Node.js 20.10.0+) +프레임워크: Express.js +데이터베이스: PostgreSQL (pg 라이브러리, Raw Query) +인증: JWT (jsonwebtoken) +스케줄러: node-cron +메일: nodemailer + IMAP +파일업로드: multer +외부DB: MySQL, MSSQL, Oracle 지원 +``` + +## 2. 계층 구조 + +``` +┌─────────────────┐ +│ Controller │ ← API 요청 수신, 응답 생성 +└────────┬────────┘ + │ +┌────────▼────────┐ +│ Service │ ← 비즈니스 로직, 트랜잭션 관리 +└────────┬────────┘ + │ +┌────────▼────────┐ +│ Database │ ← PostgreSQL Raw Query +└─────────────────┘ +``` + +## 3. 디렉토리 구조 + +``` +backend-node/src/ +├── app.ts # Express 앱 진입점 +├── config/ # 환경설정 +├── controllers/ # 70+ 컨트롤러 +├── services/ # 80+ 서비스 +├── routes/ # 70+ 라우터 +├── middleware/ # 인증/권한/에러핸들러 +├── database/ # DB 연결 (pg Pool) +├── types/ # TypeScript 타입 (26개) +└── utils/ # 유틸리티 (JWT, 암호화, 로거) +``` + +## 4. 미들웨어 스택 순서 + +```typescript +1. Process Level Exception Handlers (unhandledRejection, uncaughtException) +2. Helmet (보안 헤더) +3. Compression (Gzip) +4. Body Parser (JSON, URL-encoded, 10MB limit) +5. Static Files (/uploads) +6. CORS (credentials: true) +7. Rate Limiting (1분 10000회) +8. Token Auto Refresh (1시간 이내 만료 시 갱신) +9. API Routes (70+개) +10. 404 Handler +11. Error Handler +``` + +## 5. 인증/인가 시스템 + +### 5.1 인증 흐름 + +``` +로그인 요청 + ↓ +AuthController.login() + ↓ +AuthService.processLogin() + ├─ loginPwdCheck() → 비밀번호 검증 (bcrypt) + ├─ getPersonBeanFromSession() → 사용자 정보 조회 + ├─ insertLoginAccessLog() → 로그인 이력 저장 + └─ JwtUtils.generateToken() → JWT 토큰 생성 + ↓ +응답: { token, userInfo, firstMenuPath } +``` + +### 5.2 JWT Payload + +```json +{ + "userId": "user123", + "userName": "홍길동", + "companyCode": "ILSHIN", + "userType": "COMPANY_ADMIN", + "iat": 1234567890, + "exp": 1234654290, + "iss": "PMS-System" +} +``` + +### 5.3 권한 체계 (3단계) + +| 권한 | company_code | userType | 권한 범위 | +|------|--------------|----------|-----------| +| **SUPER_ADMIN** | `*` | `SUPER_ADMIN` | 전체 회사, DDL 실행, 회사 생성/삭제 | +| **COMPANY_ADMIN** | `ILSHIN` | `COMPANY_ADMIN` | 자기 회사만, 사용자/설정 관리 | +| **USER** | `ILSHIN` | `USER` | 자기 회사만, 읽기/쓰기 | + +## 6. 멀티테넌시 구현 + +### 핵심 원칙 +```typescript +// ✅ 올바른 패턴 +const companyCode = req.user!.companyCode; // JWT에서 추출 + +if (companyCode === "*") { + // 슈퍼관리자: 모든 데이터 조회 + query = "SELECT * FROM table ORDER BY company_code"; +} else { + // 일반 사용자: 자기 회사 + 슈퍼관리자 데이터 제외 + query = "SELECT * FROM table WHERE company_code = $1 AND company_code != '*'"; + params = [companyCode]; +} + +// ❌ 잘못된 패턴 (보안 위험!) +const companyCode = req.body.companyCode; // 클라이언트에서 받음 +``` + +### 슈퍼관리자 숨김 규칙 +```sql +-- 일반 회사 사용자에게 슈퍼관리자(company_code='*')는 보이면 안 됨 +SELECT * FROM user_info +WHERE company_code = $1 + AND company_code != '*' -- 필수! +``` + +## 7. API 라우트 (70+개) + +### 7.1 인증/관리자 +- `POST /api/auth/login` - 로그인 +- `GET /api/auth/me` - 현재 사용자 정보 +- `POST /api/auth/switch-company` - 회사 전환 (슈퍼관리자) +- `GET /api/admin/users` - 사용자 목록 +- `GET /api/admin/menus` - 메뉴 목록 + +### 7.2 테이블/화면 +- `GET /api/table-management/tables` - 테이블 목록 +- `POST /api/table-management/tables/:table/data` - 데이터 조회 +- `POST /api/table-management/multi-table-save` - 다중 테이블 저장 +- `GET /api/screen-management/screens` - 화면 목록 + +### 7.3 플로우 +- `GET /api/flow/definitions` - 플로우 정의 목록 +- `POST /api/flow/move` - 데이터 이동 (단건) +- `POST /api/flow/move-batch` - 데이터 이동 (다건) + +### 7.4 외부 연동 +- `GET /api/external-db-connections` - 외부 DB 연결 목록 +- `POST /api/external-db-connections/:id/test` - 연결 테스트 +- `POST /api/multi-connection/query` - 멀티 DB 쿼리 + +### 7.5 배치 +- `GET /api/batch-configs` - 배치 설정 목록 +- `POST /api/batch-management/:id/execute` - 배치 즉시 실행 + +### 7.6 메일 +- `POST /api/mail/send` - 메일 발송 +- `GET /api/mail/sent` - 발송 이력 + +### 7.7 파일 +- `POST /api/files/upload` - 파일 업로드 +- `GET /uploads/:filename` - 정적 파일 서빙 + +## 8. 비즈니스 도메인 (8개) + +| 도메인 | 컨트롤러 | 주요 기능 | +|--------|----------|-----------| +| **관리자** | `adminController` | 사용자/메뉴/권한 관리 | +| **테이블/화면** | `tableManagementController` | 메타데이터, 동적 화면 생성 | +| **플로우** | `flowController` | 워크플로우 엔진, 데이터 이동 | +| **데이터플로우** | `dataflowController` | ERD, 관계도 | +| **외부 연동** | `externalDbConnectionController` | 외부 DB/REST API | +| **배치** | `batchController` | Cron 스케줄러, 데이터 동기화 | +| **메일** | `mailSendSimpleController` | 메일 발송/수신 | +| **파일** | `fileController` | 파일 업로드/다운로드 | + +## 9. 데이터베이스 접근 + +### Connection Pool 설정 +```typescript +{ + min: 2~5, // 최소 연결 수 + max: 10~20, // 최대 연결 수 + connectionTimeout: 30000, // 30초 + idleTimeout: 600000, // 10분 + statementTimeout: 60000 // 쿼리 실행 60초 +} +``` + +### Raw Query 패턴 +```typescript +// 1. 다중 행 +const users = await query('SELECT * FROM user_info WHERE company_code = $1', [companyCode]); + +// 2. 단일 행 +const user = await queryOne('SELECT * FROM user_info WHERE user_id = $1', [userId]); + +// 3. 트랜잭션 +await transaction(async (client) => { + await client.query('INSERT INTO table1 ...', [...]); + await client.query('INSERT INTO table2 ...', [...]); +}); +``` + +## 10. 외부 시스템 연동 + +### 지원 데이터베이스 +- PostgreSQL +- MySQL +- Microsoft SQL Server +- Oracle + +### Connector Factory Pattern +```typescript +DatabaseConnectorFactory + ├── PostgreSQLConnector + ├── MySQLConnector + ├── MSSQLConnector + └── OracleConnector +``` + +## 11. 배치/스케줄 시스템 + +### Cron 스케줄러 +```typescript +// node-cron 기반 +// 매일 새벽 2시: "0 2 * * *" +// 5분마다: "*/5 * * * *" +// 평일 오전 8시: "0 8 * * 1-5" + +// 서버 시작 시 자동 초기화 +BatchSchedulerService.initializeScheduler(); +``` + +### 배치 실행 흐름 +``` +1. 소스 DB에서 데이터 조회 + ↓ +2. 컬럼 매핑 적용 + ↓ +3. 타겟 DB에 INSERT/UPDATE + ↓ +4. 실행 로그 기록 (batch_execution_logs) +``` + +## 12. 파일 처리 + +### 업로드 경로 +``` +uploads/ + └── {company_code}/ + └── {timestamp}-{uuid}-{filename} +``` + +### Multer 설정 +- 최대 파일 크기: 10MB +- 허용 타입: 이미지, PDF, Office 문서 +- 파일명 중복 방지: 타임스탬프 + UUID + +## 13. 보안 + +### 암호화 +- **비밀번호**: bcrypt (12 rounds) +- **민감정보**: AES-256-CBC (외부 DB 비밀번호 등) +- **JWT Secret**: 환경변수 관리 + +### 보안 헤더 +- Helmet (CSP, X-Frame-Options) +- CORS (credentials: true) +- Rate Limiting (1분 10000회) + +### SQL Injection 방지 +- Parameterized Query 사용 (pg 라이브러리) +- 동적 쿼리 빌더 패턴 + +## 14. 에러 핸들링 + +### PostgreSQL 에러 코드 매핑 +- `23505` → "중복된 데이터" +- `23503` → "참조 무결성 위반" +- `23502` → "필수 입력값 누락" + +### 프로세스 레벨 +- `unhandledRejection` → 로깅 (서버 유지) +- `uncaughtException` → 로깅 (서버 유지, 주의) +- `SIGTERM/SIGINT` → Graceful Shutdown + +## 15. 로깅 (Winston) + +### 로그 파일 +- `logs/error.log` - 에러만 (10MB × 5파일) +- `logs/combined.log` - 전체 로그 (10MB × 10파일) + +### 로그 레벨 +``` +error (0) → warn (1) → info (2) → debug (5) +``` + +## 16. 성능 최적화 + +### Pool 모니터링 +- 5분마다 상태 체크 +- 대기 연결 5개 이상 시 경고 + +### 느린 쿼리 감지 +- 1초 이상 걸린 쿼리 자동 경고 + +### 캐싱 (Redis) +- 메뉴 목록: 10분 TTL +- 공통코드: 30분 TTL + +### Gzip 압축 +- 1KB 이상 응답만 압축 (레벨 6) + +--- + +## 🎯 핵심 체크리스트 + +### 개발 시 반드시 지켜야 할 규칙 + +✅ **모든 쿼리에 `company_code` 필터 추가** +✅ **JWT 토큰에서 `company_code` 추출 (클라이언트 신뢰 금지)** +✅ **Parameterized Query 사용 (SQL Injection 방지)** +✅ **슈퍼관리자 데이터 숨김 (`company_code != '*'`)** +✅ **비밀번호는 bcrypt, 민감정보는 AES-256** +✅ **에러 핸들링 try/catch 필수** +✅ **트랜잭션이 필요한 경우 `transaction()` 사용** +✅ **파일 업로드는 회사별 디렉토리 분리** + +--- + +**문서 버전**: 1.0 +**마지막 업데이트**: 2026-02-06 diff --git a/frontend/components/screen/panels/ComponentsPanel.tsx b/frontend/components/screen/panels/ComponentsPanel.tsx index 9464a204..98302169 100644 --- a/frontend/components/screen/panels/ComponentsPanel.tsx +++ b/frontend/components/screen/panels/ComponentsPanel.tsx @@ -109,8 +109,8 @@ export function ComponentsPanel({ "v2-media", // → 테이블 컬럼의 image/file 입력 타입으로 사용 // 플로우 위젯 숨김 처리 "flow-widget", - // 선택 항목 상세입력 - 기존 컴포넌트 조합으로 대체 가능 - "selected-items-detail-input", + // 선택 항목 상세입력 - 거래처 품목 추가 등에서 사용 + // "selected-items-detail-input", // 연관 데이터 버튼 - v2-repeater로 대체 가능 "related-data-buttons", // ===== V2로 대체된 기존 컴포넌트 (v2 버전만 사용) ===== diff --git a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx index 285c655d..023002c0 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx +++ b/frontend/lib/registry/components/selected-items-detail-input/SelectedItemsDetailInputComponent.tsx @@ -94,8 +94,11 @@ export const SelectedItemsDetailInputComponent: React.FC(null); // 현재 편집 중인 품목 ID - const [editingGroupId, setEditingGroupId] = useState(null); // 현재 편집 중인 그룹 ID - const [editingDetailId, setEditingDetailId] = useState(null); // 현재 편집 중인 항목 ID + const [editingGroupId, setEditingGroupId] = useState(null); // 현재 편집 중인 그룹 ID (레거시 호환) + const [editingDetailId, setEditingDetailId] = useState(null); // 현재 편집 중인 항목 ID (레거시 호환) + + // 🆕 그룹별 독립 편집 상태: { [groupId]: entryId } + const [editingEntries, setEditingEntries] = useState>({}); // 🆕 코드 카테고리별 옵션 캐싱 const [codeOptions, setCodeOptions] = useState>>({}); @@ -404,9 +407,14 @@ export const SelectedItemsDetailInputComponent: React.FC { const fieldGroups: Record = {}; - // 각 그룹에 대해 빈 배열 초기화 + // 각 그룹에 대해 초기화 (maxEntries === 1이면 자동 1개 생성) groups.forEach((group) => { - fieldGroups[group.id] = []; + if (group.maxEntries === 1) { + // 1:1 관계: 빈 entry 1개 자동 생성 + fieldGroups[group.id] = [{ id: `${group.id}_auto_1` }]; + } else { + fieldGroups[group.id] = []; + } }); // 그룹이 없으면 기본 그룹 생성 @@ -757,6 +765,7 @@ export const SelectedItemsDetailInputComponent: React.FC ({ ...prev, [groupId]: newEntryId })); }; // 🆕 그룹 항목 제거 핸들러 @@ -992,14 +1003,34 @@ export const SelectedItemsDetailInputComponent: React.FC { + if (prev[groupId] === entryId) { + const next = { ...prev }; + delete next[groupId]; + return next; + } + return prev; + }); }; - // 🆕 그룹 항목 편집 핸들러 (클릭하면 수정 가능) + // 🆕 그룹 항목 편집 핸들러 (클릭하면 수정 가능) - 독립 편집 const handleEditGroupEntry = (itemId: string, groupId: string, entryId: string) => { setIsEditing(true); setEditingItemId(itemId); setEditingGroupId(groupId); setEditingDetailId(entryId); + // 그룹별 독립 편집: 해당 그룹만 토글 (다른 그룹은 유지) + setEditingEntries((prev) => ({ ...prev, [groupId]: entryId })); + }; + + // 🆕 특정 그룹의 편집 닫기 (다른 그룹은 유지) + const closeGroupEditing = (groupId: string) => { + setEditingEntries((prev) => { + const next = { ...prev }; + delete next[groupId]; + return next; + }); }; // 🆕 다음 품목으로 이동 @@ -1059,7 +1090,7 @@ export const SelectedItemsDetailInputComponent: React.FC handleFieldChange(itemId, groupId, entryId, field.name, e.target.value)} maxLength={field.validation?.maxLength} - className="h-10 text-sm" + className="h-7 text-xs" /> ); @@ -1081,12 +1112,12 @@ export const SelectedItemsDetailInputComponent: React.FC -
자동 계산
+
자동 계산
); } @@ -1098,7 +1129,7 @@ export const SelectedItemsDetailInputComponent: React.FC handleFieldChange(itemId, groupId, entryId, field.name, e.target.value)} min={field.validation?.min} max={field.validation?.max} - className="h-10 text-sm" + className="h-7 text-xs" /> ); @@ -1117,7 +1148,7 @@ export const SelectedItemsDetailInputComponent: React.FC ); @@ -1137,8 +1168,8 @@ export const SelectedItemsDetailInputComponent: React.FC handleFieldChange(itemId, groupId, entryId, field.name, e.target.value)} - rows={2} - className="resize-none text-xs sm:text-sm" + rows={1} + className="min-h-[28px] resize-none text-xs" /> ); @@ -1163,7 +1194,7 @@ export const SelectedItemsDetailInputComponent: React.FC handleFieldChange(itemId, groupId, entryId, field.name, val)} disabled={componentConfig.disabled || componentConfig.readonly} > - + @@ -1264,28 +1295,42 @@ export const SelectedItemsDetailInputComponent: React.FC - componentConfig.fieldGroups && componentConfig.fieldGroups.length > 0 ? f.groupId === groupId : true, - ); - return fields + // displayItems가 없으면 기본 방식 (해당 그룹의 visible 필드만 나열) + const fields = (componentConfig.additionalFields || []).filter((f) => { + // 그룹 필터 + const matchGroup = componentConfig.fieldGroups && componentConfig.fieldGroups.length > 0 + ? f.groupId === groupId + : true; + // hidden 필드 제외 (width: "0px"인 필드) + const isVisible = f.width !== "0px"; + return matchGroup && isVisible; + }); + + // 값이 있는 필드만 "라벨: 값" 형식으로 표시 + const displayParts = fields .map((f) => { const value = entry[f.name]; - if (!value) return "-"; + if (!value && value !== 0) return null; const strValue = String(value); - // 🔧 ISO 날짜 형식 자동 감지 및 포맷팅 (필드 타입 무관) - // ISO 8601 형식: YYYY-MM-DDTHH:mm:ss.sssZ 또는 YYYY-MM-DD + // ISO 날짜 형식 자동 포맷팅 const isoDateMatch = strValue.match(/^(\d{4})-(\d{2})-(\d{2})(T|\s|$)/); if (isoDateMatch) { const [, year, month, day] = isoDateMatch; - return `${year}.${month}.${day}`; + return `${f.label}: ${year}.${month}.${day}`; } - return strValue; + return `${f.label}: ${strValue}`; }) - .join(" / "); + .filter(Boolean); + + // 빈 항목 표시: 그룹 필드명으로 안내 메시지 생성 + if (displayParts.length === 0) { + const fieldLabels = fields.slice(0, 2).map(f => f.label).join("/"); + return `신규 ${fieldLabels} 입력`; + } + return displayParts.join(" "); } // displayItems 설정대로 렌더링 @@ -1459,15 +1504,117 @@ export const SelectedItemsDetailInputComponent: React.FC f.name !== "item_id" && f.width !== "0px"); + const sampleGroups = componentConfig.fieldGroups || [{ id: "default", title: "입력 정보", order: 0 }]; + const gridCols = sampleGroups.length === 1 ? "grid-cols-1" : "grid-cols-2"; + + return ( +
+
+ {/* 미리보기 안내 배너 */} +
+ [미리보기] + 실제 데이터가 전달되면 아래와 같은 형태로 표시됩니다 +
+ + {/* 샘플 품목 카드 2개 */} + {[1, 2].map((idx) => ( + + + + + {idx}. {sampleDisplayCols.length > 0 ? `샘플 ${sampleDisplayCols[0]?.label || "품목"} ${idx}` : `샘플 항목 ${idx}`} + + + + {sampleDisplayCols.length > 0 && ( +
+ {sampleDisplayCols.map((col, i) => ( + + {i > 0 && " | "} + {col.label}: 샘플값 + + ))} +
+ )} +
+ +
+ {sampleGroups.map((group) => { + const groupFields = sampleFields.filter(f => + sampleGroups.length <= 1 || f.groupId === group.id + ); + if (groupFields.length === 0) return null; + const isSingle = group.maxEntries === 1; + + return ( + + + + {group.title} + {!isSingle && ( + + )} + + + + {isSingle ? ( + /* 1:1 그룹: 인라인 폼 미리보기 */ +
+ {groupFields.slice(0, 4).map(f => ( +
+ {f.label} +
샘플
+
+ ))} + {groupFields.length > 4 && ( +
외 {groupFields.length - 4}개 필드
+ )} +
+ ) : ( + /* 1:N 그룹: 다중 항목 미리보기 */ + <> +
+ + 1. {groupFields.slice(0, 2).map(f => `${f.label}: 샘플`).join(" / ")} + + +
+ {idx === 1 && ( +
+ + 2. {groupFields.slice(0, 2).map(f => `${f.label}: 샘플`).join(" / ")} + + +
+ )} + + )} +
+
+ ); + })} +
+
+
+ ))} +
+
+ ); + } + + // 런타임 빈 상태 return (

{componentConfig.emptyMessage}

- {isDesignMode && ( -

- 💡 이전 모달에서 "다음" 버튼으로 데이터를 전달하면 여기에 표시됩니다. -

- )}
); @@ -1483,141 +1630,147 @@ export const SelectedItemsDetailInputComponent: React.FC (a.order || 0) - (b.order || 0)); + // 그룹 수에 따라 grid 열 수 결정 + const gridCols = sortedGroups.length === 1 ? "grid-cols-1" : "grid-cols-2"; + return ( -
+
{sortedGroups.map((group) => { const groupFields = fields.filter((f) => (groups.length === 0 ? true : f.groupId === group.id)); if (groupFields.length === 0) return null; const groupEntries = item.fieldGroups[group.id] || []; - const isEditingThisGroup = isEditing && editingItemId === item.id && editingGroupId === group.id; + // 그룹별 독립 편집 상태 사용 + const editingEntryIdForGroup = editingEntries[group.id] || null; + + // 1:1 관계 그룹 (maxEntries === 1): 인라인 폼으로 바로 표시 + const isSingleEntry = group.maxEntries === 1; + const singleEntry = isSingleEntry ? (groupEntries[0] || { id: `${group.id}_auto_1` }) : null; + // hidden 필드 제외 (width: "0px"인 필드) + const visibleFields = groupFields.filter((f) => f.width !== "0px"); return ( - + {group.title} - + {/* 1:N 그룹만 + 추가 버튼 표시 */} + {!isSingleEntry && ( + + )} - {group.description &&

{group.description}

} + {group.description &&

{group.description}

}
- - {/* 이미 입력된 항목들 */} - {groupEntries.length > 0 ? ( -
- {groupEntries.map((entry, idx) => { - const isEditingThisEntry = isEditingThisGroup && editingDetailId === entry.id; - - if (isEditingThisEntry) { - // 편집 모드: 입력 필드 표시 (가로 배치) - return ( - - -
- 수정 중 - -
- {/* 🆕 가로 Grid 배치 (2~3열) */} -
- {groupFields.map((field) => ( -
- - {renderField(field, item.id, group.id, entry.id, entry)} -
- ))} -
-
-
- ); - } else { - // 읽기 모드: 텍스트 표시 (클릭하면 수정) - return ( -
handleEditGroupEntry(item.id, group.id, entry.id)} - > - - {idx + 1}. {renderDisplayItems(entry, item, group.id)} - - -
- ); - } - })} + + {/* === 1:1 그룹: 인라인 폼 (항상 편집 모드) - 컴팩트 === */} + {isSingleEntry && singleEntry && ( +
+ {visibleFields.map((field) => ( +
+ + {renderField(field, item.id, group.id, singleEntry.id, singleEntry)} +
+ ))}
- ) : ( -

아직 입력된 항목이 없습니다.

)} - {/* 새 항목 입력 중 */} - {isEditingThisGroup && editingDetailId && !groupEntries.find((e) => e.id === editingDetailId) && ( - - -
- 새 항목 - + {/* === 1:N 그룹: 다중 입력 (독립 편집) === */} + {!isSingleEntry && ( + <> + {groupEntries.length > 0 ? ( +
+ {groupEntries.map((entry, idx) => { + // 그룹별 독립 편집 상태 확인 + const isEditingThisEntry = editingEntryIdForGroup === entry.id; + + return ( +
+ {/* 헤더 (항상 표시) */} +
{ + if (isEditingThisEntry) { + // 이 그룹만 닫기 (다른 그룹은 유지) + closeGroupEditing(group.id); + } else { + handleEditGroupEntry(item.id, group.id, entry.id); + } + }} + > + + {idx + 1}. {renderDisplayItems(entry, item, group.id)} + +
+ +
+
+ + {/* 폼 영역 (편집 시에만 아래로 펼침) - 컴팩트 */} + {isEditingThisEntry && ( +
+
+ {visibleFields.map((field) => ( +
+ + {renderField(field, item.id, group.id, entry.id, entry)} +
+ ))} +
+
+ +
+
+ )} +
+ ); + })}
- {groupFields.map((field) => ( -
- - {renderField(field, item.id, group.id, editingDetailId, {})} -
- ))} - - + ) : ( +

아직 입력된 항목이 없습니다.

+ )} + + {/* 새 항목은 handleAddGroupEntry에서 아코디언 항목으로 직접 추가됨 */} + )} diff --git a/frontend/lib/registry/components/selected-items-detail-input/types.ts b/frontend/lib/registry/components/selected-items-detail-input/types.ts index 88d02c8e..d1778074 100644 --- a/frontend/lib/registry/components/selected-items-detail-input/types.ts +++ b/frontend/lib/registry/components/selected-items-detail-input/types.ts @@ -54,6 +54,8 @@ export interface FieldGroup { description?: string; /** 그룹 표시 순서 */ order?: number; + /** 🆕 최대 항목 수 (1이면 1:1 관계 - 자동 생성, + 추가 버튼 숨김) */ + maxEntries?: number; /** 🆕 이 그룹의 항목 표시 설정 (그룹별로 다른 표시 형식 가능) */ displayItems?: DisplayItem[]; }