From 08dde416b1ca3281e0f5d57dccc3e4875b6205a4 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Fri, 6 Feb 2026 16:00:43 +0900 Subject: [PATCH] docs: Add detailed backend, database, and frontend architecture analysis documents - Created a comprehensive analysis document for the backend architecture, detailing the directory structure, API routes, authentication workflows, and more. - Added a database architecture analysis report outlining the database structure, multi-tenancy architecture, and key system tables. - Introduced a frontend architecture analysis document that covers the directory structure, component systems, and Next.js App Router structure. These documents aim to enhance the understanding of the WACE ERP system's architecture and facilitate better workflow documentation. --- docs/DB_ARCHITECTURE_ANALYSIS.md | 2188 ++++++++++++++++++++++++ docs/backend-architecture-analysis.md | 1424 +++++++++++++++ docs/frontend-architecture-analysis.md | 1920 +++++++++++++++++++++ mcp-agent-orchestrator/src/index.ts | 127 +- 4 files changed, 5612 insertions(+), 47 deletions(-) create mode 100644 docs/DB_ARCHITECTURE_ANALYSIS.md create mode 100644 docs/backend-architecture-analysis.md create mode 100644 docs/frontend-architecture-analysis.md diff --git a/docs/DB_ARCHITECTURE_ANALYSIS.md b/docs/DB_ARCHITECTURE_ANALYSIS.md new file mode 100644 index 00000000..084c6940 --- /dev/null +++ b/docs/DB_ARCHITECTURE_ANALYSIS.md @@ -0,0 +1,2188 @@ +# WACE ERP 데이터베이스 아키텍처 분석 보고서 + +> 📅 작성일: 2026-01-20 +> 🎯 목적: WACE ERP 시스템 전체 워크플로우 문서화를 위한 DB 구조 분석 +> 📊 DB 엔진: PostgreSQL 16.8 + +--- + +## 📋 목차 + +1. [개요](#1-개요) +2. [전체 테이블 목록](#2-전체-테이블-목록) +3. [멀티테넌시 아키텍처](#3-멀티테넌시-아키텍처) +4. [핵심 시스템 테이블](#4-핵심-시스템-테이블) +5. [메타데이터 관리 시스템](#5-메타데이터-관리-시스템) +6. [화면 관리 시스템](#6-화면-관리-시스템) +7. [비즈니스 도메인별 테이블](#7-비즈니스-도메인별-테이블) +8. [플로우 및 데이터 통합](#8-플로우-및-데이터-통합) +9. [인덱스 전략](#9-인덱스-전략) +10. [동적 테이블 생성 패턴](#10-동적-테이블-생성-패턴) +11. [마이그레이션 히스토리](#11-마이그레이션-히스토리) + +--- + +## 1. 개요 + +### 1.1 데이터베이스 통계 + +``` +- 총 테이블 수: 약 280개 +- 총 함수 수: 약 50개 +- 총 트리거 수: 약 30개 +- 총 시퀀스 수: 약 100개 +- 뷰 수: 약 20개 +``` + +### 1.2 아키텍처 특징 + +- **멀티테넌시**: 모든 테이블에 `company_code` 컬럼으로 회사별 데이터 격리 +- **동적 스키마**: 런타임에 테이블 생성/수정 가능 +- **메타데이터 드리븐**: UI 컴포넌트가 메타데이터 테이블을 기반으로 동적 렌더링 +- **이력 관리**: 주요 테이블에 `_log` 테이블로 변경 이력 추적 +- **외부 연동**: 외부 DB 및 REST API 연결 지원 +- **플로우 기반**: 화면 간 데이터 흐름을 정의하고 실행 + +--- + +## 2. 전체 테이블 목록 + +### 2.1 테이블 분류 체계 + +``` +시스템 관리 (약 30개) +├── 사용자/권한 (10개) +├── 메뉴 관리 (5개) +├── 회사 관리 (3개) +└── 공통 코드 (5개) + +메타데이터 시스템 (약 20개) +├── 테이블/컬럼 정의 (8개) +├── 화면 정의 (10개) +└── 레이아웃/컴포넌트 (5개) + +비즈니스 도메인 (약 200개) +├── 영업/수주 (30개) +├── 구매/발주 (25개) +├── 재고/창고 (20개) +├── 생산/작업 (25개) +├── 품질/검사 (15개) +├── 물류/운송 (20개) +├── PLM/설계 (30개) +├── 회계/원가 (20개) +└── 기타 (15개) + +통합/플로우 (약 30개) +├── 데이터플로우 (10개) +├── 배치 작업 (8개) +└── 외부 연동 (12개) +``` + +### 2.2 주요 테이블 목록 (알파벳순) + +
+전체 테이블 목록 보기 (280개) + +``` +approval +attach_file_info +auth_tokens +authority_master +authority_master_history +authority_sub_user +batch_configs +batch_execution_logs +batch_job_executions +batch_job_parameters +batch_jobs +batch_mappings +batch_schedules +button_action_standards +carrier_contract_mng +carrier_contract_mng_log +carrier_mng +carrier_mng_log +carrier_vehicle_mng +carrier_vehicle_mng_log +cascading_auto_fill_group +cascading_auto_fill_mapping +cascading_condition +cascading_hierarchy_group +cascading_hierarchy_level +cascading_multi_parent +cascading_multi_parent_source +cascading_mutual_exclusion +cascading_relation +cascading_reverse_lookup +category_column_mapping +category_value_cascading_group +category_value_cascading_mapping +chartmgmt +check_report_mng +code_category +code_info +collection_batch_executions +collection_batch_management +column_labels +comm_code +comm_code_history +comm_exchange_rate +comments +company_code_sequence +company_mng +component_standards +contract_mgmt +contract_mgmt_option +counselingmgmt +customer_item +customer_item_alias +customer_item_mapping +customer_item_price +customer_mng +customer_service_mgmt +customer_service_part +customer_service_workingtime +dashboard_elements +dashboard_shares +dashboard_slider_items +dashboard_sliders +dashboards +data_collection_configs +data_collection_history +data_collection_jobs +data_relationship_bridge +dataflow_diagrams +dataflow_external_calls +ddl_execution_log +defect_standard_mng +defect_standard_mng_log +delivery_destination +delivery_history +delivery_history_defect +delivery_part_price +delivery_route_mng +delivery_route_mng_log +delivery_status +dept_info +dept_info_history +digital_twin_layout +digital_twin_layout_template +digital_twin_location_layout +digital_twin_objects +digital_twin_zone_layout +drivers +dtg_contracts +dtg_maintenance_history +dtg_management +dtg_management_log +dtg_monthly_settlements +dynamic_form_data +equipment_consumable +equipment_consumable_log +equipment_inspection_item +equipment_inspection_item_log +equipment_mng +equipment_mng_log +estimate_mgmt +excel_mapping_template +expense_detail +expense_master +external_call_configs +external_call_logs +external_connection_permission +external_db_connection +external_db_connections +external_rest_api_connections +external_work_review_info +facility_assembly_plan +file_down_log +flow_audit_log +flow_data_mapping +flow_data_status +flow_definition +flow_external_connection_permission +flow_external_db_connection +flow_integration_log +flow_step +flow_step_connection +fund_mgmt +grid_standards +inbound_mng +inboxtask +injection_cost +input_cost_goal +input_resource +inspection_equipment_mng +inspection_equipment_mng_log +inspection_standard +inventory_history +inventory_stock +item_info +item_inspection_info +item_routing_detail +item_routing_version +klbom_tbl +language_master +layout_instances +layout_standards +login_access_log +logistics_cost_mng +logistics_cost_mng_log +mail_log +maintenance_schedules +material_cost +material_detail_mgmt +material_master_mgmt +material_mng +material_release +menu_info +menu_screen_group_items +menu_screen_groups +mold_dev_request_info +multi_lang_category +multi_lang_key_master +multi_lang_text +node_flows +numbering_rule_parts +numbering_rules +oem_factory_mng +oem_milestone_mng +oem_mng +option_mng +option_price_history +order_mgmt +order_mng_master +order_mng_sub +order_plan_mgmt +order_plan_result_error +order_spec_mng +order_spec_mng_history +outbound_mng +part_bom_qty +part_bom_report +part_distribution_list +part_mgmt +part_mng +part_mng_history +planning_issue +pms_invest_cost_mng +pms_pjt_concept_info +pms_pjt_info +pms_pjt_year_goal +pms_rel_pjt_concept_milestone +pms_rel_pjt_concept_prod +pms_rel_pjt_prod +pms_rel_prod_ref_dept +pms_wbs_task +pms_wbs_task_confirm +pms_wbs_task_info +pms_wbs_task_standard +pms_wbs_template +problem_mng +process_equipment +process_mng +procurement_standard +product_group_mng +product_kind_spec +product_kind_spec_main +product_mgmt +product_mgmt_model +product_mgmt_price_history +product_mgmt_upg_detail +product_mgmt_upg_master +product_mng +product_spec +production_issue +production_record +production_task +profit_loss +profit_loss_coefficient +profit_loss_coolingtime +profit_loss_depth +profit_loss_lossrate +profit_loss_machine +profit_loss_pretime +profit_loss_srrate +profit_loss_total +profit_loss_total_addlist +profit_loss_weight +project +project_mgmt +purchase_detail +purchase_order +purchase_order_master +purchase_order_mng +purchase_order_multi +purchase_order_part +ratecal_mgmt +receive_history +receiving +rel_menu_auth +report_layout +report_master +report_menu_mapping +report_query +report_template +safety_budget_execution +safety_incidents +safety_inspections +safety_inspections_log +sales_bom_part_qty +sales_bom_report +sales_bom_report_part +sales_long_delivery +sales_long_delivery_input +sales_long_delivery_predict +sales_order_detail +sales_order_detail_log +sales_order_mng +sales_part_chg +sales_request_master +sales_request_part +sample_supply +screen_data_flows +screen_data_transfer +screen_definitions +screen_embedding +screen_field_joins +screen_group_members +screen_group_screens +screen_groups +screen_layouts +screen_menu_assignments +screen_split_panel +screen_table_relations +screen_templates +screen_widgets +shipment_detail +shipment_header +shipment_instruction +shipment_instruction_item +shipment_pallet +shipment_plan +standard_doc_info +structural_review_proposal +style_templates +supplier_item +supplier_item_alias +supplier_item_mapping +supplier_item_price +supplier_mng +supplier_mng_log +supply_charger_mng +supply_mng +supply_mng_history +table_column_category_values +table_labels +table_log_config +table_relationships +table_type_columns +tax_invoice +tax_invoice_item +time_sheet +transport_logs +transport_statistics +transport_vehicle_locations +used_mng +user_dept +user_dept_sub +user_info +user_info_history +vehicle_location_history +vehicle_locations +vehicle_trip_summary +vehicles +warehouse_info +warehouse_location +web_type_standards +work_instruction +work_instruction_detail +work_instruction_detail_log +work_instruction_log +work_order +work_orders +work_orders_detail +work_request +yard_layout +yard_material_placement +``` + +
+ +--- + +## 3. 멀티테넌시 아키텍처 + +### 3.1 company_code 패턴 + +**모든 테이블에 필수적으로 포함되는 컬럼:** + +```sql +company_code VARCHAR(20) NOT NULL +``` + +**의미:** +- 하나의 데이터베이스에서 여러 회사의 데이터를 격리 +- 모든 쿼리는 반드시 `company_code` 필터 포함 필요 + +### 3.2 특별한 company_code 값 + +#### `company_code = "*"` 의미 + +```sql +-- ❌ 잘못된 이해: 모든 회사가 공유하는 공통 데이터 +-- ✅ 올바른 이해: 슈퍼 관리자 전용 데이터 + +-- 일반 회사는 "*" 데이터를 볼 수 없음 +SELECT * FROM table_name +WHERE company_code = 'COMPANY_A' + AND company_code != '*'; -- 필수! +``` + +**용도:** +- 시스템 관리자용 메타데이터 +- 전역 설정 값 +- 기본 템플릿 + +### 3.3 멀티테넌시 쿼리 패턴 + +```sql +-- ✅ 표준 SELECT 패턴 +SELECT * FROM table_name +WHERE company_code = $1 + AND company_code != '*' +ORDER BY created_date DESC; + +-- ✅ JOIN 패턴 (company_code 매칭 필수!) +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 * +FROM orders o +WHERE company_code = $1 + AND product_id IN ( + SELECT id FROM products + WHERE company_code = $1 -- 서브쿼리에도 필수! + ); + +-- ✅ 집계 패턴 +SELECT + product_type, + COUNT(*) as total, + SUM(amount) as total_amount +FROM sales +WHERE company_code = $1 +GROUP BY product_type; +``` + +### 3.4 company_code 인덱스 전략 + +**모든 테이블에 필수 인덱스:** + +```sql +CREATE INDEX idx_{table_name}_company_code +ON {table_name}(company_code); + +-- 복합 인덱스 예시 +CREATE INDEX idx_sales_company_date +ON sales(company_code, sale_date DESC); +``` + +--- + +## 4. 핵심 시스템 테이블 + +### 4.1 사용자 관리 + +#### user_info (사용자 정보) + +```sql +CREATE TABLE user_info ( + sabun VARCHAR(1024), -- 사번 + user_id VARCHAR(1024) PRIMARY KEY,-- 사용자 ID + user_password VARCHAR(1024), -- 암호화된 비밀번호 + user_name VARCHAR(1024), -- 한글명 + user_name_eng VARCHAR(1024), -- 영문명 + user_name_cn VARCHAR(1024), -- 중문명 + dept_code VARCHAR(1024), -- 부서 코드 + dept_name VARCHAR(1024), -- 부서명 + position_code VARCHAR(1024), -- 직위 코드 + position_name VARCHAR(1024), -- 직위명 + email VARCHAR(1024), -- 이메일 + tel VARCHAR(1024), -- 전화번호 + cell_phone VARCHAR(1024), -- 휴대폰 + user_type VARCHAR(1024), -- 사용자 유형 코드 + user_type_name VARCHAR(1024), -- 사용자 유형명 + company_code VARCHAR(50), -- 회사 코드 (멀티테넌시) + status VARCHAR(32), -- active/inactive + license_number VARCHAR(50), -- 면허번호 + vehicle_number VARCHAR(50), -- 차량번호 + signup_type VARCHAR(20), -- 가입 유형 + branch_name VARCHAR(100), -- 지점명 + regdate TIMESTAMP, -- 등록일 + end_date TIMESTAMP -- 종료일 +); +``` + +**관련 테이블:** +- `user_info_history`: 사용자 정보 변경 이력 +- `user_dept`: 사용자-부서 관계 +- `user_dept_sub`: 사용자 하위 부서 + +#### auth_tokens (인증 토큰) + +```sql +CREATE TABLE auth_tokens ( + id SERIAL PRIMARY KEY, + user_id VARCHAR(255) NOT NULL, + token VARCHAR(500) NOT NULL, + refresh_token VARCHAR(500), + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + ip_address VARCHAR(50), + user_agent TEXT +); +``` + +### 4.2 권한 관리 + +#### authority_master (권한 그룹) + +```sql +CREATE TABLE authority_master ( + objid NUMERIC PRIMARY KEY, + auth_code VARCHAR(64), -- 권한 코드 + auth_name VARCHAR(64), -- 권한명 + company_code VARCHAR(50), -- 회사 코드 + status VARCHAR(32), -- 상태 + writer VARCHAR(32), -- 작성자 + regdate TIMESTAMP -- 등록일 +); +``` + +**관련 테이블:** +- `authority_master_history`: 권한 변경 이력 +- `authority_sub_user`: 권한-사용자 매핑 +- `rel_menu_auth`: 권한-메뉴 매핑 + +### 4.3 메뉴 관리 + +#### menu_info (메뉴 정보) + +```sql +CREATE TABLE menu_info ( + objid NUMERIC PRIMARY KEY, + menu_type NUMERIC, -- 0=일반, 1=시스템관리, 2=동적생성 + parent_obj_id NUMERIC, -- 부모 메뉴 ID + menu_name_kor VARCHAR(64), -- 한글 메뉴명 + menu_name_eng VARCHAR(64), -- 영문 메뉴명 + menu_code VARCHAR(50), -- 메뉴 코드 + menu_url VARCHAR(256), -- 메뉴 URL + seq NUMERIC, -- 순서 + screen_code VARCHAR(50), -- 화면 코드 (동적 생성 시) + screen_group_id INTEGER, -- 화면 그룹 ID + company_code VARCHAR(50), -- 회사 코드 + status VARCHAR(32), -- active/inactive + lang_key VARCHAR(100), -- 다국어 키 + source_menu_objid BIGINT, -- 원본 메뉴 ID (복사 시) + writer VARCHAR(32), + regdate TIMESTAMP +); +``` + +**특징:** +- `menu_type = 2`: 화면 생성 시 자동으로 생성되는 메뉴 +- 트리거: `auto_create_menu_for_screen()` - 화면 생성 시 자동 메뉴 추가 + +**관련 테이블:** +- `menu_screen_groups`: 메뉴 화면 그룹 +- `menu_screen_group_items`: 그룹-화면 연결 + +### 4.4 회사 관리 + +#### company_mng (회사 정보) + +```sql +CREATE TABLE company_mng ( + company_code VARCHAR(32) PRIMARY KEY, + company_name VARCHAR(64), + business_registration_number VARCHAR(20), -- 사업자등록번호 + representative_name VARCHAR(100), -- 대표자명 + representative_phone VARCHAR(20), -- 대표 연락처 + email VARCHAR(255), -- 회사 이메일 + website VARCHAR(500), -- 웹사이트 + address VARCHAR(500), -- 주소 + status VARCHAR(32), + writer VARCHAR(32), + regdate TIMESTAMP +); +``` + +**관련 테이블:** +- `company_code_sequence`: 회사별 시퀀스 관리 + +### 4.5 부서 관리 + +#### dept_info (부서 정보) + +```sql +CREATE TABLE dept_info ( + dept_code VARCHAR(1024) PRIMARY KEY, + dept_name VARCHAR(1024), + parent_dept_code VARCHAR(1024), -- 상위 부서 + company_code VARCHAR(50), + status VARCHAR(32), + writer VARCHAR(32), + regdate TIMESTAMP +); +``` + +**관련 테이블:** +- `dept_info_history`: 부서 정보 변경 이력 + +--- + +## 5. 메타데이터 관리 시스템 + +WACE ERP의 핵심 특징은 **메타데이터 드리븐 아키텍처**입니다. 화면, 테이블, 컬럼 정보를 메타데이터 테이블에서 관리하고, 프론트엔드가 이를 기반으로 동적 렌더링합니다. + +### 5.1 테이블 메타데이터 + +#### table_labels (테이블 정의) + +```sql +CREATE TABLE table_labels ( + table_name VARCHAR(100) PRIMARY KEY, -- 테이블명 (물리명) + table_label VARCHAR(200), -- 테이블 한글명 + description TEXT, -- 설명 + use_log_table VARCHAR(1) DEFAULT 'N', -- 이력 테이블 사용 여부 + created_date TIMESTAMP, + updated_date TIMESTAMP +); +``` + +**역할:** +- 동적으로 생성된 모든 테이블의 메타정보 저장 +- 화면 생성 시 테이블 선택 목록 제공 +- 데이터 딕셔너리로 활용 + +#### table_type_columns (컬럼 타입 정의) + +```sql +CREATE TABLE table_type_columns ( + id SERIAL PRIMARY KEY, + table_name VARCHAR(255) NOT NULL, + column_name VARCHAR(255) NOT NULL, + company_code VARCHAR(20) NOT NULL, -- 회사별 컬럼 설정 + input_type VARCHAR(50) DEFAULT 'text',-- 입력 타입 + detail_settings TEXT DEFAULT '{}', -- JSON 상세 설정 + is_nullable VARCHAR(10) DEFAULT 'Y', + display_order INTEGER DEFAULT 0, -- 표시 순서 + created_date TIMESTAMP, + updated_date TIMESTAMP, + + UNIQUE(table_name, column_name, company_code) +); +``` + +**input_type 종류:** +- `text`: 일반 텍스트 +- `number`: 숫자 +- `date`: 날짜 +- `select`: 드롭다운 (options 필요) +- `textarea`: 여러 줄 텍스트 +- `entity`: 참조 테이블 (referenceTable, referenceColumn 필요) +- `checkbox`: 체크박스 +- `radio`: 라디오 버튼 + +**detail_settings 예시:** + +```json +// select 타입 +{ + "options": [ + {"label": "일반", "value": "normal"}, + {"label": "긴급", "value": "urgent"} + ] +} + +// entity 타입 +{ + "referenceTable": "customer_mng", + "referenceColumn": "customer_code", + "displayColumn": "customer_name" +} +``` + +#### column_labels (컬럼 라벨 - 레거시) + +```sql +CREATE TABLE column_labels ( + table_name VARCHAR(100) NOT NULL, + column_name VARCHAR(100) NOT NULL, + column_label VARCHAR(200), -- 한글 라벨 + input_type VARCHAR(50), + detail_settings TEXT, + description TEXT, + display_order INTEGER, + is_visible BOOLEAN DEFAULT true, + created_date TIMESTAMP, + updated_date TIMESTAMP, + + PRIMARY KEY (table_name, column_name) +); +``` + +**참고:** +- 레거시 호환을 위해 유지 +- 새로운 컬럼은 `table_type_columns` 사용 권장 +- `table_type_columns`는 회사별 설정, `column_labels`는 전역 설정 + +### 5.2 카테고리 값 관리 + +#### table_column_category_values (컬럼 카테고리 값) + +```sql +CREATE TABLE table_column_category_values ( + id SERIAL PRIMARY KEY, + table_name VARCHAR(255) NOT NULL, + column_name VARCHAR(255) NOT NULL, + company_code VARCHAR(20) NOT NULL, + category_value VARCHAR(500) NOT NULL, -- 카테고리 값 + display_label VARCHAR(500), -- 표시 라벨 + display_order INTEGER DEFAULT 0, + is_active VARCHAR(1) DEFAULT 'Y', + parent_value VARCHAR(500), -- 부모 카테고리 (계층 구조) + created_date TIMESTAMP, + updated_date TIMESTAMP, + + UNIQUE(table_name, column_name, company_code, category_value) +); +``` + +**용도:** +- 동적 드롭다운 값 관리 +- 계층형 카테고리 지원 (parent_value) +- 회사별 카테고리 값 커스터마이징 + +**관련 테이블:** +- `category_column_mapping`: 카테고리-컬럼 매핑 +- `category_value_cascading_group`: 카테고리 캐스케이딩 그룹 +- `category_value_cascading_mapping`: 캐스케이딩 매핑 + +### 5.3 테이블 관계 관리 + +#### table_relationships (테이블 관계) + +```sql +CREATE TABLE table_relationships ( + id SERIAL PRIMARY KEY, + parent_table VARCHAR(100), -- 부모 테이블 + parent_column VARCHAR(100), -- 부모 컬럼 + child_table VARCHAR(100), -- 자식 테이블 + child_column VARCHAR(100), -- 자식 컬럼 + relationship_type VARCHAR(20), -- one-to-many, many-to-one 등 + created_date TIMESTAMP +); +``` + +--- + +## 6. 화면 관리 시스템 + +WACE ERP는 코드 작성 없이 화면을 동적으로 생성/수정할 수 있는 **Low-Code 플랫폼** 기능을 제공합니다. + +### 6.1 화면 정의 + +#### screen_definitions (화면 정의) + +```sql +CREATE TABLE screen_definitions ( + screen_id SERIAL PRIMARY KEY, + screen_name VARCHAR(100) NOT NULL, -- 화면명 + screen_code VARCHAR(50) NOT NULL, -- 화면 코드 (URL용) + table_name VARCHAR(100) NOT NULL, -- 메인 테이블 + company_code VARCHAR(50) NOT NULL, + description TEXT, + is_active CHAR(1) DEFAULT 'Y', -- Y=활성, N=비활성, D=삭제 + layout_metadata JSONB, -- 레이아웃 JSON + + -- 외부 데이터 소스 지원 + db_source_type VARCHAR(10) DEFAULT 'internal', -- internal/external + db_connection_id INTEGER, -- 외부 DB 연결 ID + data_source_type VARCHAR(20) DEFAULT 'database', -- database/rest_api + rest_api_connection_id INTEGER, -- REST API 연결 ID + rest_api_endpoint VARCHAR(500), -- API 엔드포인트 + rest_api_json_path VARCHAR(200) DEFAULT 'data', -- JSON 응답 경로 + + source_screen_id INTEGER, -- 원본 화면 ID (복사 시) + + created_date TIMESTAMP NOT NULL DEFAULT NOW(), + created_by VARCHAR(50), + updated_date TIMESTAMP NOT NULL DEFAULT NOW(), + updated_by VARCHAR(50), + deleted_date TIMESTAMP, -- 휴지통 이동 시점 + deleted_by VARCHAR(50), + delete_reason TEXT, + + UNIQUE(screen_code, company_code) +); +``` + +**화면 생성 플로우:** +1. 관리자가 화면 설정 페이지에서 테이블 선택 +2. `screen_definitions` 레코드 생성 +3. 트리거 `auto_create_menu_for_screen()` 실행 → `menu_info` 자동 생성 +4. 프론트엔드가 `/screen/{screen_code}` 경로로 접근 시 동적 렌더링 + +#### screen_layouts (화면 레이아웃 - 레거시) + +```sql +CREATE TABLE screen_layouts ( + layout_id SERIAL PRIMARY KEY, + screen_id INTEGER REFERENCES screen_definitions(screen_id), + layout_name VARCHAR(100), + layout_type VARCHAR(50), -- grid, form, split, tab 등 + layout_config JSONB, -- 레이아웃 설정 + display_order INTEGER, + is_active CHAR(1) DEFAULT 'Y', + company_code VARCHAR(50), + created_date TIMESTAMP, + updated_date TIMESTAMP +); +``` + +### 6.2 화면 그룹 관리 + +#### screen_groups (화면 그룹) + +```sql +CREATE TABLE screen_groups ( + id SERIAL PRIMARY KEY, + group_name VARCHAR(100) NOT NULL, -- 그룹명 + group_code VARCHAR(50) NOT NULL, -- 그룹 코드 + main_table_name VARCHAR(100), -- 메인 테이블 + description TEXT, + icon VARCHAR(100), -- 아이콘 + display_order INT DEFAULT 0, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20) NOT NULL, + + -- 계층 구조 지원 (037 마이그레이션에서 추가) + parent_group_id INTEGER REFERENCES screen_groups(id) ON DELETE CASCADE, + group_level INTEGER DEFAULT 0, -- 0=대, 1=중, 2=소 + hierarchy_path VARCHAR(500), -- 예: /1/3/5/ + + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + writer VARCHAR(50), + + UNIQUE(company_code, group_code) +); + +CREATE INDEX idx_screen_groups_company_code ON screen_groups(company_code); +CREATE INDEX idx_screen_groups_parent_id ON screen_groups(parent_group_id); +CREATE INDEX idx_screen_groups_hierarchy_path ON screen_groups(hierarchy_path); +``` + +#### screen_group_screens (화면-그룹 연결) + +```sql +CREATE TABLE screen_group_screens ( + id SERIAL PRIMARY KEY, + group_id INT NOT NULL REFERENCES screen_groups(id) ON DELETE CASCADE, + screen_id INT NOT NULL REFERENCES screen_definitions(screen_id) ON DELETE CASCADE, + screen_role VARCHAR(50) DEFAULT 'main', -- main, register, list, detail 등 + display_order INT DEFAULT 0, + is_default VARCHAR(1) DEFAULT 'N', -- 기본 화면 여부 + company_code VARCHAR(20) NOT NULL, + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + writer VARCHAR(50), + + UNIQUE(group_id, screen_id) +); +``` + +**용도:** +- 관련 화면들을 그룹으로 묶어 관리 +- 예: "영업 관리" 그룹 → 견적 화면, 수주 화면, 출하 화면 + +### 6.3 화면 필드 조인 + +#### screen_field_joins (화면 필드 조인 설정) + +```sql +CREATE TABLE screen_field_joins ( + id SERIAL PRIMARY KEY, + screen_id INT NOT NULL REFERENCES screen_definitions(screen_id) ON DELETE CASCADE, + layout_id INT, + component_id VARCHAR(500), + field_name VARCHAR(100), + + -- 저장 테이블 설정 + save_table VARCHAR(100) NOT NULL, + save_column VARCHAR(100) NOT NULL, + + -- 조인 테이블 설정 + join_table VARCHAR(100) NOT NULL, + join_column VARCHAR(100) NOT NULL, + display_column VARCHAR(100) NOT NULL, + + -- 조인 옵션 + join_type VARCHAR(20) DEFAULT 'LEFT', + filter_condition TEXT, + sort_column VARCHAR(100), + sort_direction VARCHAR(10) DEFAULT 'ASC', + + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20) NOT NULL, + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + writer VARCHAR(50) +); +``` + +**예시:** +```json +{ + "save_table": "sales_order", + "save_column": "customer_code", + "join_table": "customer_mng", + "join_column": "customer_code", + "display_column": "customer_name" +} +``` + +### 6.4 화면 간 데이터 흐름 + +#### screen_data_flows (화면 간 데이터 흐름) + +```sql +CREATE TABLE screen_data_flows ( + id SERIAL PRIMARY KEY, + group_id INT REFERENCES screen_groups(id) ON DELETE SET NULL, + + -- 소스 화면 + source_screen_id INT NOT NULL REFERENCES screen_definitions(screen_id) ON DELETE CASCADE, + source_action VARCHAR(50), -- click, submit, select 등 + + -- 타겟 화면 + target_screen_id INT NOT NULL REFERENCES screen_definitions(screen_id) ON DELETE CASCADE, + target_action VARCHAR(50), -- open, load, refresh 등 + + -- 데이터 매핑 설정 + data_mapping JSONB, -- 필드 매핑 정보 + + -- 흐름 설정 + flow_type VARCHAR(20) DEFAULT 'unidirectional', -- unidirectional/bidirectional + flow_label VARCHAR(100), -- 시각화 라벨 + condition_expression TEXT, -- 실행 조건식 + + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20) NOT NULL, + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + writer VARCHAR(50) +); +``` + +**data_mapping 예시:** + +```json +{ + "customer_code": "customer_code", + "customer_name": "customer_name", + "selected_date": "order_date" +} +``` + +#### screen_table_relations (화면-테이블 관계) + +```sql +CREATE TABLE screen_table_relations ( + id SERIAL PRIMARY KEY, + group_id INT REFERENCES screen_groups(id) ON DELETE SET NULL, + screen_id INT NOT NULL REFERENCES screen_definitions(screen_id) ON DELETE CASCADE, + table_name VARCHAR(100) NOT NULL, + + relation_type VARCHAR(20) DEFAULT 'main', -- main, join, lookup + crud_operations VARCHAR(20) DEFAULT 'CRUD',-- CRUD 조합 + description TEXT, + + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20) NOT NULL, + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + writer VARCHAR(50) +); +``` + +### 6.5 컴포넌트 표준 + +#### component_standards (컴포넌트 표준) + +```sql +CREATE TABLE component_standards ( + component_code VARCHAR(50) PRIMARY KEY, + component_name VARCHAR(100) NOT NULL, + component_name_eng VARCHAR(100), + description TEXT, + category VARCHAR(50) NOT NULL, -- input, layout, display 등 + icon_name VARCHAR(50), + default_size JSON, -- {width, height} + component_config JSON NOT NULL, -- 컴포넌트 설정 + preview_image VARCHAR(255), + sort_order INTEGER DEFAULT 0, + is_active CHAR(1) DEFAULT 'Y', + is_public CHAR(1) DEFAULT 'Y', + company_code VARCHAR(50) NOT NULL, + created_date TIMESTAMP, + updated_date TIMESTAMP +); +``` + +#### layout_standards (레이아웃 표준) + +```sql +CREATE TABLE layout_standards ( + layout_code VARCHAR(50) PRIMARY KEY, + layout_name VARCHAR(100) NOT NULL, + layout_type VARCHAR(50), -- grid, form, split, tab + default_config JSON, + is_active CHAR(1) DEFAULT 'Y', + company_code VARCHAR(50), + created_date TIMESTAMP, + updated_date TIMESTAMP +); +``` + +--- + +## 7. 비즈니스 도메인별 테이블 + +### 7.1 영업/수주 관리 + +#### 수주 관리 (Order Management) + +``` +sales_order_mng -- 수주 마스터 +├── sales_order_detail -- 수주 상세 +├── sales_order_detail_log -- 수주 상세 이력 +├── sales_request_master -- 영업 요청 마스터 +├── sales_request_part -- 영업 요청 부품 +└── sales_part_chg -- 영업 부품 변경 +``` + +**sales_order_mng:** +- 고객별 수주 정보 +- 납기, 금액, 상태 관리 + +**sales_order_detail:** +- 수주 라인 아이템 +- 품목, 수량, 단가 정보 + +#### 견적 관리 + +``` +estimate_mgmt -- 견적 관리 +contract_mgmt -- 계약 관리 +├── contract_mgmt_option -- 계약 옵션 +``` + +#### BOM 관리 + +``` +sales_bom_report -- 영업 BOM 리포트 +├── sales_bom_report_part -- 영업 BOM 부품 +└── sales_bom_part_qty -- 영업 BOM 부품 수량 +``` + +### 7.2 구매/발주 관리 + +``` +purchase_order_master -- 발주 마스터 +├── purchase_order -- 발주 상세 +├── purchase_order_part -- 발주 부품 +├── purchase_order_multi -- 다중 발주 +└── purchase_detail -- 구매 상세 + +supplier_mng -- 공급업체 관리 +├── supplier_mng_log -- 공급업체 이력 +├── supplier_item -- 공급업체 품목 +├── supplier_item_alias -- 공급업체 품목 별칭 +├── supplier_item_mapping -- 공급업체 품목 매핑 +└── supplier_item_price -- 공급업체 품목 가격 +``` + +### 7.3 재고/창고 관리 + +``` +inventory_stock -- 재고 현황 +inventory_history -- 재고 이력 +warehouse_info -- 창고 정보 +warehouse_location -- 창고 위치 +inbound_mng -- 입고 관리 +outbound_mng -- 출고 관리 +receiving -- 입하 +receive_history -- 입하 이력 +``` + +### 7.4 생산/작업 관리 + +``` +work_orders -- 작업지시 (신규) +├── work_orders_detail -- 작업지시 상세 +work_order -- 작업지시 (레거시) +work_instruction -- 작업 지시서 +├── work_instruction_detail -- 작업 지시서 상세 +├── work_instruction_detail_log +└── work_instruction_log + +production_record -- 생산 실적 +production_task -- 생산 작업 +production_issue -- 생산 이슈 +work_request -- 작업 요청 +``` + +#### work_orders (작업지시 - 050 마이그레이션) + +```sql +CREATE TABLE work_orders ( + 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), + + -- 작업지시 정보 + wo_number VARCHAR(500), -- WO-20250130-001 + product_code VARCHAR(500), + product_name VARCHAR(500), + spec VARCHAR(500), + order_qty VARCHAR(500), + completed_qty VARCHAR(500), + start_date VARCHAR(500), + due_date VARCHAR(500), + status VARCHAR(500), -- normal/urgent + progress_status VARCHAR(500), -- pending/in-progress/completed + equipment VARCHAR(500), + routing VARCHAR(500), + work_team VARCHAR(500), -- DAY/NIGHT + worker VARCHAR(500), + shift VARCHAR(500), -- DAY/NIGHT + remark VARCHAR(500) +); + +CREATE INDEX idx_work_orders_company_code ON work_orders(company_code); +CREATE INDEX idx_work_orders_wo_number ON work_orders(wo_number); +``` + +### 7.5 품질/검사 관리 + +``` +inspection_standard -- 검사 기준 +item_inspection_info -- 품목 검사 정보 +inspection_equipment_mng -- 검사 설비 관리 +├── inspection_equipment_mng_log +defect_standard_mng -- 불량 기준 관리 +├── defect_standard_mng_log +check_report_mng -- 검사 성적서 관리 +safety_inspections -- 안전 점검 +└── safety_inspections_log +``` + +### 7.6 물류/운송 관리 + +``` +vehicles -- 차량 정보 +├── vehicle_locations -- 차량 위치 +├── vehicle_location_history -- 차량 위치 이력 +├── vehicle_trip_summary -- 차량 운행 요약 +drivers -- 운전자 정보 +transport_logs -- 운송 로그 +transport_statistics -- 운송 통계 +transport_vehicle_locations -- 차량 위치 + +carrier_mng -- 운송사 관리 +├── carrier_mng_log +├── carrier_contract_mng -- 운송사 계약 +├── carrier_contract_mng_log +├── carrier_vehicle_mng -- 운송사 차량 +└── carrier_vehicle_mng_log + +delivery_route_mng -- 배송 경로 관리 +├── delivery_route_mng_log +delivery_destination -- 배송지 +delivery_status -- 배송 상태 +delivery_history -- 배송 이력 +├── delivery_history_defect -- 배송 불량 +delivery_part_price -- 배송 부품 가격 +``` + +#### DTG 관리 (디지털 타코그래프) + +``` +dtg_management -- DTG 관리 +├── dtg_management_log +dtg_contracts -- DTG 계약 +dtg_maintenance_history -- DTG 정비 이력 +dtg_monthly_settlements -- DTG 월별 정산 +``` + +### 7.7 PLM/설계 관리 + +``` +part_mng -- 부품 관리 (메인) +├── part_mng_history +part_mgmt -- 부품 관리 (서브) +part_bom_qty -- 부품 BOM 수량 +part_bom_report -- 부품 BOM 리포트 +part_distribution_list -- 부품 배포 목록 + +item_info -- 품목 정보 +item_routing_version -- 품목 라우팅 버전 +item_routing_detail -- 품목 라우팅 상세 + +product_mng -- 제품 관리 +product_mgmt -- 제품 관리 (메인) +├── product_mgmt_model -- 제품 모델 +├── product_mgmt_price_history -- 제품 가격 이력 +├── product_mgmt_upg_master -- 제품 업그레이드 마스터 +└── product_mgmt_upg_detail -- 제품 업그레이드 상세 + +product_kind_spec -- 제품 종류 사양 +product_kind_spec_main -- 제품 종류 사양 메인 +product_spec -- 제품 사양 +product_group_mng -- 제품 그룹 관리 + +mold_dev_request_info -- 금형 개발 요청 +structural_review_proposal -- 구조 검토 제안 +``` + +### 7.8 프로젝트 관리 (PMS) + +``` +pms_pjt_info -- 프로젝트 정보 +├── pms_pjt_concept_info -- 프로젝트 개념 정보 +├── pms_pjt_year_goal -- 프로젝트 연도 목표 +pms_wbs_task -- WBS 작업 +├── pms_wbs_task_info -- WBS 작업 정보 +├── pms_wbs_task_confirm -- WBS 작업 확인 +├── pms_wbs_task_standard -- WBS 작업 표준 +└── pms_wbs_template -- WBS 템플릿 + +pms_rel_pjt_concept_milestone -- 프로젝트 개념-마일스톤 관계 +pms_rel_pjt_concept_prod -- 프로젝트 개념-제품 관계 +pms_rel_pjt_prod -- 프로젝트-제품 관계 +pms_rel_prod_ref_dept -- 제품-참조부서 관계 + +pms_invest_cost_mng -- 투자 비용 관리 +project_mgmt -- 프로젝트 관리 +problem_mng -- 문제 관리 +planning_issue -- 계획 이슈 +``` + +### 7.9 회계/원가 관리 + +``` +tax_invoice -- 세금계산서 +├── tax_invoice_item -- 세금계산서 항목 +fund_mgmt -- 자금 관리 +expense_master -- 비용 마스터 +├── expense_detail -- 비용 상세 + +profit_loss -- 손익 계산 +├── profit_loss_total -- 손익 합계 +├── profit_loss_coefficient -- 손익 계수 +├── profit_loss_machine -- 손익 기계 +├── profit_loss_weight -- 손익 무게 +├── profit_loss_depth -- 손익 깊이 +├── profit_loss_pretime -- 손익 사전 시간 +├── profit_loss_coolingtime -- 손익 냉각 시간 +├── profit_loss_lossrate -- 손익 손실률 +└── profit_loss_srrate -- 손익 SR률 + +material_cost -- 자재 비용 +injection_cost -- 사출 비용 +logistics_cost_mng -- 물류 비용 관리 +└── logistics_cost_mng_log + +input_cost_goal -- 투입 비용 목표 +input_resource -- 투입 자원 +``` + +### 7.10 고객/협력사 관리 + +``` +customer_mng -- 고객 관리 +customer_item -- 고객 품목 +├── customer_item_alias -- 고객 품목 별칭 +├── customer_item_mapping -- 고객 품목 매핑 +└── customer_item_price -- 고객 품목 가격 + +customer_service_mgmt -- 고객 서비스 관리 +├── customer_service_part -- 고객 서비스 부품 +└── customer_service_workingtime -- 고객 서비스 작업시간 + +oem_mng -- OEM 관리 +├── oem_factory_mng -- OEM 공장 관리 +└── oem_milestone_mng -- OEM 마일스톤 관리 +``` + +### 7.11 설비/장비 관리 + +``` +equipment_mng -- 설비 관리 +├── equipment_mng_log +equipment_consumable -- 설비 소모품 +├── equipment_consumable_log +equipment_inspection_item -- 설비 검사 항목 +└── equipment_inspection_item_log + +process_equipment -- 공정 설비 +process_mng -- 공정 관리 +maintenance_schedules -- 정비 일정 +``` + +### 7.12 기타 + +``` +approval -- 결재 +comments -- 댓글 +inboxtask -- 수신함 작업 +time_sheet -- 작업 시간 +attach_file_info -- 첨부 파일 +file_down_log -- 파일 다운로드 로그 +login_access_log -- 로그인 접근 로그 +``` + +--- + +## 8. 플로우 및 데이터 통합 + +### 8.1 플로우 정의 + +#### flow_definition (플로우 정의) + +```sql +CREATE TABLE flow_definition ( + flow_id SERIAL PRIMARY KEY, + flow_name VARCHAR(200) NOT NULL, + flow_code VARCHAR(100) NOT NULL, + flow_type VARCHAR(50), -- data_transfer, approval, batch 등 + description TEXT, + trigger_type VARCHAR(50), -- manual, schedule, event + trigger_config JSONB, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + created_by VARCHAR(50), + updated_by VARCHAR(50), + + UNIQUE(flow_code, company_code) +); +``` + +#### flow_step (플로우 단계) + +```sql +CREATE TABLE flow_step ( + step_id SERIAL PRIMARY KEY, + flow_id INTEGER REFERENCES flow_definition(flow_id) ON DELETE CASCADE, + step_name VARCHAR(200) NOT NULL, + step_type VARCHAR(50) NOT NULL, -- query, transform, api_call, condition 등 + step_order INTEGER NOT NULL, + step_config JSONB NOT NULL, + error_handling_config JSONB, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW() +); +``` + +#### flow_step_connection (플로우 단계 연결) + +```sql +CREATE TABLE flow_step_connection ( + connection_id SERIAL PRIMARY KEY, + flow_id INTEGER REFERENCES flow_definition(flow_id) ON DELETE CASCADE, + source_step_id INTEGER REFERENCES flow_step(step_id) ON DELETE CASCADE, + target_step_id INTEGER REFERENCES flow_step(step_id) ON DELETE CASCADE, + condition_expression TEXT, -- 조건부 실행 + connection_type VARCHAR(20) DEFAULT 'sequential', -- sequential, parallel, conditional + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW() +); +``` + +### 8.2 데이터플로우 + +#### dataflow_diagrams (데이터플로우 다이어그램) + +```sql +CREATE TABLE dataflow_diagrams ( + diagram_id SERIAL PRIMARY KEY, + diagram_name VARCHAR(200) NOT NULL, + diagram_type VARCHAR(50), + diagram_json JSONB NOT NULL, -- 다이어그램 시각화 정보 + description TEXT, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + created_by VARCHAR(50), + updated_by VARCHAR(50) +); +``` + +#### flow_data_mapping (플로우 데이터 매핑) + +```sql +CREATE TABLE flow_data_mapping ( + mapping_id SERIAL PRIMARY KEY, + flow_id INTEGER REFERENCES flow_definition(flow_id) ON DELETE CASCADE, + source_type VARCHAR(20), -- table, api, flow + source_identifier VARCHAR(200), -- 테이블명 또는 API 엔드포인트 + source_field VARCHAR(100), + target_type VARCHAR(20), + target_identifier VARCHAR(200), + target_field VARCHAR(100), + transformation_rule TEXT, -- 변환 규칙 (JavaScript 표현식) + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW() +); +``` + +#### flow_data_status (플로우 데이터 상태) + +```sql +CREATE TABLE flow_data_status ( + status_id SERIAL PRIMARY KEY, + flow_id INTEGER REFERENCES flow_definition(flow_id), + execution_id VARCHAR(100), + source_table VARCHAR(100), + source_record_id VARCHAR(500), + target_table VARCHAR(100), + target_record_id VARCHAR(500), + status VARCHAR(20), -- pending, processing, completed, failed + error_message TEXT, + processed_at TIMESTAMPTZ, + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW() +); +``` + +### 8.3 외부 연동 + +#### external_db_connections (외부 DB 연결) + +```sql +CREATE TABLE external_db_connections ( + id SERIAL PRIMARY KEY, + connection_name VARCHAR(200) NOT NULL, + connection_code VARCHAR(100) NOT NULL, + db_type VARCHAR(50) NOT NULL, -- postgresql, mysql, mssql, oracle + host VARCHAR(255) NOT NULL, + port INTEGER NOT NULL, + database_name VARCHAR(100) NOT NULL, + username VARCHAR(100), + password_encrypted TEXT, + ssl_enabled BOOLEAN DEFAULT false, + connection_options JSONB, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + created_by VARCHAR(50), + + UNIQUE(connection_code, company_code) +); +``` + +#### external_rest_api_connections (외부 REST API 연결) + +```sql +CREATE TABLE external_rest_api_connections ( + id SERIAL PRIMARY KEY, + connection_name VARCHAR(200) NOT NULL, + connection_code VARCHAR(100) NOT NULL, + base_url VARCHAR(500) NOT NULL, + auth_type VARCHAR(50), -- none, basic, bearer, api_key + auth_config JSONB, + default_headers JSONB, + timeout_seconds INTEGER DEFAULT 30, + retry_count INTEGER DEFAULT 0, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + + UNIQUE(connection_code, company_code) +); +``` + +#### external_call_configs (외부 호출 설정) + +```sql +CREATE TABLE external_call_configs ( + id SERIAL PRIMARY KEY, + config_name VARCHAR(200) NOT NULL, + config_code VARCHAR(100) NOT NULL, + connection_id INTEGER, -- external_rest_api_connections 참조 + http_method VARCHAR(10), -- GET, POST, PUT, DELETE + endpoint_path VARCHAR(500), + request_mapping JSONB, -- 요청 데이터 매핑 + response_mapping JSONB, -- 응답 데이터 매핑 + error_handling JSONB, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW() +); +``` + +### 8.4 배치 작업 + +#### batch_jobs (배치 작업 정의) + +```sql +CREATE TABLE batch_jobs ( + job_id SERIAL PRIMARY KEY, + job_name VARCHAR(200) NOT NULL, + job_code VARCHAR(100) NOT NULL, + job_type VARCHAR(50), -- data_collection, aggregation, sync 등 + source_type VARCHAR(50), -- database, api, file + source_config JSONB, + target_config JSONB, + schedule_config JSONB, + is_active VARCHAR(1) DEFAULT 'Y', + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW(), + updated_date TIMESTAMPTZ DEFAULT NOW(), + + UNIQUE(job_code, company_code) +); +``` + +#### batch_execution_logs (배치 실행 로그) + +```sql +CREATE TABLE batch_execution_logs ( + execution_id SERIAL PRIMARY KEY, + job_id INTEGER REFERENCES batch_jobs(job_id), + execution_status VARCHAR(20), -- running, completed, failed + start_time TIMESTAMPTZ, + end_time TIMESTAMPTZ, + records_processed INTEGER, + records_failed INTEGER, + error_message TEXT, + execution_details JSONB, + company_code VARCHAR(20), + created_date TIMESTAMPTZ DEFAULT NOW() +); +``` + +--- + +## 9. 인덱스 전략 + +### 9.1 필수 인덱스 + +**모든 테이블에 적용:** + +```sql +-- company_code 인덱스 (멀티테넌시 성능) +CREATE INDEX idx_{table_name}_company_code ON {table_name}(company_code); + +-- 복합 인덱스 (company_code + 주요 검색 컬럼) +CREATE INDEX idx_{table_name}_company_{column} +ON {table_name}(company_code, {column}); +``` + +### 9.2 화면 관련 인덱스 + +```sql +-- screen_definitions +CREATE INDEX idx_screen_definitions_company_code ON screen_definitions(company_code); +CREATE INDEX idx_screen_definitions_table_name ON screen_definitions(table_name); +CREATE INDEX idx_screen_definitions_is_active ON screen_definitions(is_active); +CREATE UNIQUE INDEX idx_screen_definitions_code_company +ON screen_definitions(screen_code, company_code); + +-- screen_groups +CREATE INDEX idx_screen_groups_company_code ON screen_groups(company_code); +CREATE INDEX idx_screen_groups_parent_id ON screen_groups(parent_group_id); +CREATE INDEX idx_screen_groups_hierarchy_path ON screen_groups(hierarchy_path); + +-- screen_field_joins +CREATE INDEX idx_screen_field_joins_screen_id ON screen_field_joins(screen_id); +CREATE INDEX idx_screen_field_joins_save_table ON screen_field_joins(save_table); +CREATE INDEX idx_screen_field_joins_join_table ON screen_field_joins(join_table); +``` + +### 9.3 메타데이터 인덱스 + +```sql +-- table_type_columns +CREATE UNIQUE INDEX idx_table_type_columns_unique +ON table_type_columns(table_name, column_name, company_code); + +-- column_labels +CREATE INDEX idx_column_labels_table ON column_labels(table_name); +``` + +### 9.4 비즈니스 테이블 인덱스 예시 + +```sql +-- sales_order_mng +CREATE INDEX idx_sales_order_company_code ON sales_order_mng(company_code); +CREATE INDEX idx_sales_order_customer ON sales_order_mng(customer_code); +CREATE INDEX idx_sales_order_date ON sales_order_mng(order_date DESC); +CREATE INDEX idx_sales_order_status ON sales_order_mng(order_status); + +-- work_orders +CREATE INDEX idx_work_orders_company_code ON work_orders(company_code); +CREATE INDEX idx_work_orders_wo_number ON work_orders(wo_number); +CREATE INDEX idx_work_orders_start_date ON work_orders(start_date); +CREATE INDEX idx_work_orders_product_code ON work_orders(product_code); +``` + +### 9.5 플로우 관련 인덱스 + +```sql +-- flow_definition +CREATE UNIQUE INDEX idx_flow_definition_code_company +ON flow_definition(flow_code, company_code); + +-- flow_step +CREATE INDEX idx_flow_step_flow_id ON flow_step(flow_id); +CREATE INDEX idx_flow_step_order ON flow_step(flow_id, step_order); + +-- flow_data_status +CREATE INDEX idx_flow_data_status_flow_execution +ON flow_data_status(flow_id, execution_id); +CREATE INDEX idx_flow_data_status_source +ON flow_data_status(source_table, source_record_id); +``` + +--- + +## 10. 동적 테이블 생성 패턴 + +WACE ERP의 핵심 기능 중 하나는 **런타임에 테이블을 동적으로 생성**할 수 있다는 것입니다. + +### 10.1 표준 컬럼 구조 + +**모든 동적 생성 테이블의 기본 컬럼:** + +```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)) + {user_column_1} VARCHAR(500), + {user_column_2} VARCHAR(500), + ... +); + +-- 필수 인덱스 +CREATE INDEX idx_{table_name}_company_code ON {table_name}(company_code); +``` + +### 10.2 메타데이터 등록 프로세스 + +동적 테이블 생성 시 반드시 수행해야 하는 작업: + +#### 1단계: 테이블 생성 + +```sql +CREATE TABLE {table_name} ( + -- 위의 표준 구조 참조 +); +``` + +#### 2단계: table_labels 등록 + +```sql +INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) +VALUES ('{table_name}', '{한글명}', '{설명}', NOW(), NOW()) +ON CONFLICT (table_name) +DO UPDATE SET + table_label = EXCLUDED.table_label, + description = EXCLUDED.description, + updated_date = NOW(); +``` + +#### 3단계: table_type_columns 등록 + +```sql +-- 기본 컬럼 등록 (display_order: -5 ~ -1) +INSERT INTO table_type_columns ( + table_name, column_name, company_code, input_type, detail_settings, + is_nullable, display_order, created_date, updated_date +) VALUES + ('{table_name}', 'id', '{company_code}', 'text', '{}', 'Y', -5, NOW(), NOW()), + ('{table_name}', 'created_date', '{company_code}', 'date', '{}', 'Y', -4, NOW(), NOW()), + ('{table_name}', 'updated_date', '{company_code}', 'date', '{}', 'Y', -3, NOW(), NOW()), + ('{table_name}', 'writer', '{company_code}', 'text', '{}', 'Y', -2, NOW(), NOW()), + ('{table_name}', 'company_code', '{company_code}', 'text', '{}', 'Y', -1, NOW(), NOW()) +ON CONFLICT (table_name, column_name, company_code) +DO UPDATE SET + input_type = EXCLUDED.input_type, + display_order = EXCLUDED.display_order, + updated_date = NOW(); + +-- 사용자 정의 컬럼 등록 (display_order: 0부터 시작) +INSERT INTO table_type_columns ( + table_name, column_name, company_code, input_type, detail_settings, + is_nullable, display_order, created_date, updated_date +) VALUES + ('{table_name}', '{column_1}', '{company_code}', 'text', '{}', 'Y', 0, NOW(), NOW()), + ('{table_name}', '{column_2}', '{company_code}', 'number', '{}', 'Y', 1, NOW(), NOW()), + ... +``` + +#### 4단계: column_labels 등록 (레거시 호환용) + +```sql +INSERT INTO column_labels ( + table_name, column_name, column_label, input_type, detail_settings, + description, display_order, is_visible, created_date, updated_date +) VALUES + ('{table_name}', 'id', 'ID', 'text', '{}', '기본키', -5, true, NOW(), NOW()), + ... +ON CONFLICT (table_name, column_name) +DO UPDATE SET + column_label = EXCLUDED.column_label, + input_type = EXCLUDED.input_type, + updated_date = NOW(); +``` + +### 10.3 마이그레이션 예시 + +**050_create_work_orders_table.sql:** + +```sql +-- ============================================ +-- 1. 테이블 생성 +-- ============================================ +CREATE TABLE work_orders ( + 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), + + wo_number VARCHAR(500), + product_code VARCHAR(500), + product_name VARCHAR(500), + ... +); + +CREATE INDEX idx_work_orders_company_code ON work_orders(company_code); +CREATE INDEX idx_work_orders_wo_number ON work_orders(wo_number); + +-- ============================================ +-- 2. table_labels 등록 +-- ============================================ +INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) +VALUES ('work_orders', '작업지시', '작업지시 관리 테이블', NOW(), NOW()) +ON CONFLICT (table_name) DO UPDATE SET ...; + +-- ============================================ +-- 3. table_type_columns 등록 +-- ============================================ +INSERT INTO table_type_columns (...) VALUES (...); + +-- ============================================ +-- 4. column_labels 등록 +-- ============================================ +INSERT INTO column_labels (...) VALUES (...); + +-- ============================================ +-- 5. 코멘트 추가 +-- ============================================ +COMMENT ON TABLE work_orders IS '작업지시 관리 테이블'; +COMMENT ON COLUMN work_orders.wo_number IS '작업지시번호 (WO-YYYYMMDD-XXX)'; +``` + +--- + +## 11. 마이그레이션 히스토리 + +### 11.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 -- 화면 관리 기능 확장 +└── plm_schema_20260120.sql -- 전체 스키마 덤프 +``` + +### 11.2 주요 마이그레이션 내용 + +#### 037: 화면 그룹 계층 구조 + +- `screen_groups`에 계층 구조 지원 추가 +- `parent_group_id`, `group_level`, `hierarchy_path` 컬럼 추가 +- 대/중/소 분류 지원 + +#### 050~052: 작업지시 시스템 + +- `work_orders` 테이블 생성 +- 메타데이터 등록 (table_labels, table_type_columns, column_labels) +- 화면 정의 및 레이아웃 생성 + +#### 054: 화면 관리 기능 확장 + +- `screen_groups`: 화면 그룹 +- `screen_group_screens`: 화면-그룹 연결 +- `screen_field_joins`: 화면 필드 조인 설정 +- `screen_data_flows`: 화면 간 데이터 흐름 +- `screen_table_relations`: 화면-테이블 관계 + +### 11.3 마이그레이션 실행 가이드 + +**마이그레이션 문서:** +- `RUN_027_MIGRATION.md` +- `RUN_043_MIGRATION.md` +- `RUN_044_MIGRATION.md` +- `RUN_046_MIGRATION.md` +- `RUN_063_064_MIGRATION.md` +- `RUN_065_MIGRATION.md` +- `RUN_078_MIGRATION.md` + +--- + +## 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) RETURNS INTEGER; +``` + +#### 거리 계산 + +```sql +-- Haversine 거리 계산 +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; +``` + +#### 로그 관리 + +```sql +-- 테이블 변경 로그 트리거 함수 +CREATE FUNCTION carrier_contract_mng_log_trigger_func() RETURNS TRIGGER; +CREATE FUNCTION carrier_mng_log_trigger_func() 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(); +``` + +--- + +## 13. 뷰 (Views) + +### 13.1 시스템 뷰 + +```sql +-- 권한 그룹 요약 +CREATE VIEW v_authority_group_summary AS +SELECT + am.objid, + am.auth_name, + am.auth_code, + am.company_code, + (SELECT COUNT(*) FROM authority_sub_user WHERE master_objid = am.objid) AS member_count, + (SELECT COUNT(*) FROM rel_menu_auth WHERE auth_objid = am.objid) AS menu_count +FROM authority_master am; +``` + +--- + +## 14. 데이터베이스 보안 및 암호화 + +### 14.1 암호화 컬럼 + +```sql +-- external_db_connections +password_encrypted TEXT -- AES 암호화된 비밀번호 + +-- external_rest_api_connections +auth_config JSONB -- 암호화된 인증 정보 +``` + +### 14.2 접근 제어 + +- PostgreSQL 롤 기반 접근 제어 +- 회사별 데이터 격리 (company_code) +- 사용자별 권한 관리 (authority_master, rel_menu_auth) + +--- + +## 15. 성능 최적화 전략 + +### 15.1 인덱스 최적화 + +- **company_code 인덱스**: 모든 테이블에 필수 +- **복합 인덱스**: 자주 함께 조회되는 컬럼 조합 +- **부분 인덱스**: 특정 조건의 데이터만 인덱싱 + +### 15.2 쿼리 최적화 + +- **서브쿼리 최소화**: JOIN으로 대체 +- **EXPLAIN ANALYZE** 활용 +- **인덱스 힌트** 사용 + +### 15.3 캐싱 전략 + +- **참조 데이터 캐싱**: referenceCacheService.ts +- **Redis 캐싱**: 자주 조회되는 메타데이터 + +### 15.4 파티셔닝 + +- 대용량 이력 테이블 파티셔닝 고려 +- 날짜 기반 파티셔닝 (vehicle_location_history 등) + +--- + +## 16. 백업 및 복구 + +### 16.1 백업 전략 + +```bash +# 전체 스키마 백업 +pg_dump -h host -U user -d database > plm_schema_YYYYMMDD.sql + +# 데이터 포함 백업 +pg_dump -h host -U user -d database --data-only > data_YYYYMMDD.sql + +# 특정 테이블 백업 +pg_dump -h host -U user -d database -t table_name > table_backup.sql +``` + +### 16.2 마이그레이션 롤백 + +- DDL 작업 전 백업 필수 +- 트랜잭션 기반 마이그레이션 +- 롤백 스크립트 준비 + +--- + +## 17. 모니터링 및 로깅 + +### 17.1 시스템 로그 테이블 + +``` +login_access_log -- 로그인 접근 로그 +ddl_execution_log -- DDL 실행 로그 +batch_execution_logs -- 배치 실행 로그 +flow_audit_log -- 플로우 감사 로그 +flow_integration_log -- 플로우 통합 로그 +external_call_logs -- 외부 호출 로그 +mail_log -- 메일 발송 로그 +file_down_log -- 파일 다운로드 로그 +``` + +### 17.2 변경 이력 테이블 + +**패턴:** `{원본테이블}_log` 또는 `{원본테이블}_history` + +``` +user_info_history +dept_info_history +authority_master_history +comm_code_history +carrier_mng_log +supplier_mng_log +equipment_mng_log +... +``` + +--- + +## 18. 결론 + +### 18.1 핵심 아키텍처 특징 + +1. **멀티테넌시**: company_code로 완벽한 데이터 격리 +2. **메타데이터 드리븐**: 동적 화면/테이블 생성 +3. **Low-Code 플랫폼**: 코드 없이 화면 구축 +4. **플로우 기반**: 시각적 데이터 흐름 설계 +5. **외부 연동**: DB/API 통합 지원 +6. **이력 관리**: 완벽한 변경 이력 추적 + +### 18.2 확장성 + +- **수평 확장**: 멀티테넌시로 무한한 회사 추가 가능 +- **수직 확장**: 동적 테이블/컬럼 추가 +- **기능 확장**: 플로우/배치 작업으로 비즈니스 로직 추가 + +### 18.3 유지보수성 + +- **표준화된 구조**: 모든 테이블이 동일한 패턴 +- **자동화**: 트리거/함수로 반복 작업 자동화 +- **문서화**: 메타데이터 테이블 자체가 문서 + +--- + +## 부록 A: 백엔드 서비스 매핑 + +### 주요 서비스와 테이블 매핑 + +```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 +``` + +--- + +## 부록 B: SQL 쿼리 예시 + +### 멀티테넌시 표준 쿼리 + +```sql +-- ✅ 단일 테이블 조회 +SELECT * FROM sales_order_mng +WHERE company_code = $1 + AND company_code != '*' + AND order_date >= $2 +ORDER BY order_date DESC +LIMIT 100; + +-- ✅ JOIN 쿼리 +SELECT + so.order_no, + so.order_date, + c.customer_name, + p.product_name, + so.quantity, + so.unit_price +FROM sales_order_mng so +LEFT JOIN customer_mng c + ON so.customer_code = c.customer_code + AND so.company_code = c.company_code +LEFT JOIN product_mng p + ON so.product_code = p.product_code + AND so.company_code = p.company_code +WHERE so.company_code = $1 + AND so.company_code != '*' + AND so.order_date BETWEEN $2 AND $3; + +-- ✅ 집계 쿼리 +SELECT + customer_code, + COUNT(*) as order_count, + SUM(total_amount) as total_sales +FROM sales_order_mng +WHERE company_code = $1 + AND company_code != '*' + AND order_date >= DATE_TRUNC('month', CURRENT_DATE) +GROUP BY customer_code +HAVING COUNT(*) >= 5 +ORDER BY total_sales DESC; +``` + +--- + +## 부록 C: 참고 문서 + +``` +docs/ +├── DDD1542/ +│ ├── DB_STRUCTURE_DIAGRAM.md -- DB 구조 다이어그램 +│ ├── DB_INEFFICIENCY_ANALYSIS.md -- DB 비효율성 분석 +│ ├── COMPONENT_URL_SYSTEM_IMPLEMENTATION.md +│ ├── V2_마이그레이션_학습노트_DDD1542.md +│ └── 본서버_개발서버_마이그레이션_가이드.md +├── backend-architecture-analysis.md -- 백엔드 아키텍처 분석 +└── screen-implementation-guide/ -- 화면 구현 가이드 +``` + +--- + +**문서 작성자**: Cursor AI (DB Specialist Agent) +**문서 버전**: 1.0 +**마지막 업데이트**: 2026-01-20 +**스키마 버전**: plm_schema_20260120.sql + +--- diff --git a/docs/backend-architecture-analysis.md b/docs/backend-architecture-analysis.md new file mode 100644 index 00000000..c5c2b549 --- /dev/null +++ b/docs/backend-architecture-analysis.md @@ -0,0 +1,1424 @@ +# WACE ERP 백엔드 아키텍처 상세 분석 + +> **작성일**: 2026-02-06 +> **분석 대상**: ERP-node/backend-node +> **Stack**: Node.js + Express + TypeScript + PostgreSQL Raw Query + +--- + +## 📋 목차 + +1. [전체 디렉토리 구조](#1-전체-디렉토리-구조) +2. [API 라우트 목록 및 역할](#2-api-라우트-목록-및-역할) +3. [인증/인가 워크플로우](#3-인증인가-워크플로우) +4. [비즈니스 도메인별 모듈 분류](#4-비즈니스-도메인별-모듈-분류) +5. [미들웨어 스택 구성](#5-미들웨어-스택-구성) +6. [서비스 레이어 패턴](#6-서비스-레이어-패턴) +7. [멀티테넌시 구현 방식](#7-멀티테넌시-구현-방식) +8. [에러 핸들링 전략](#8-에러-핸들링-전략) +9. [파일 업로드/다운로드 처리](#9-파일-업로드다운로드-처리) +10. [외부 연동](#10-외부-연동) +11. [배치/스케줄 처리](#11-배치스케줄-처리) +12. [컨트롤러/서비스 상세 역할](#12-컨트롤러서비스-상세-역할) + +--- + +## 1. 전체 디렉토리 구조 + +``` +backend-node/ +├── src/ +│ ├── app.ts # Express 앱 진입점, 라우트 등록, 미들웨어 설정 +│ ├── config/ +│ │ └── environment.ts # 환경변수 관리 (PORT, DB, JWT, CORS 등) +│ ├── controllers/ # 69개 컨트롤러 (요청 처리 및 응답) +│ ├── services/ # 87개 서비스 (비즈니스 로직) +│ ├── routes/ # 77개 라우터 (엔드포인트 정의) +│ ├── middleware/ # 4개 미들웨어 (인증, 권한, 에러 핸들링) +│ ├── database/ # DB 연결 풀, 커넥터, 마이그레이션 +│ ├── utils/ # 16개 유틸리티 (JWT, 암호화, 로거 등) +│ ├── types/ # 26개 TypeScript 타입 정의 +│ ├── interfaces/ # 인터페이스 정의 +│ └── tests/ # 테스트 파일 +├── scripts/ # 배치 및 유틸리티 스크립트 +├── data/ # JSON 기반 설정 데이터 +├── uploads/ # 파일 업로드 디렉토리 +└── package.json # 의존성 관리 +``` + +### 주요 특징 +- **Layered Architecture**: Controller → Service → Database 3계층 구조 +- **TypeScript Strict Mode**: 타입 안전성 보장 +- **Raw Query 기반**: Prisma → PostgreSQL Raw Query 전환 완료 +- **Connection Pool**: pg 라이브러리 기반 연결 풀 관리 +- **마이크로서비스 지향**: 도메인별 명확한 분리 + +--- + +## 2. API 라우트 목록 및 역할 + +### 2.1 인증 및 관리자 (Auth & Admin) + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/auth/login` | POST | 로그인 (JWT 토큰 발급) | ❌ | +| `/api/auth/signup` | POST | 회원가입 (공차중계) | ❌ | +| `/api/auth/me` | GET | 현재 사용자 정보 조회 | ✅ | +| `/api/auth/logout` | POST | 로그아웃 | ✅ | +| `/api/auth/refresh` | POST | JWT 토큰 갱신 | ✅ | +| `/api/auth/switch-company` | POST | 관리자 전용: 회사 전환 | ✅ | +| `/api/admin/menus` | GET | 메뉴 목록 조회 | ✅ | +| `/api/admin/users` | GET/POST/PUT | 사용자 관리 (CRUD) | ✅ | +| `/api/admin/companies` | GET/POST/PUT/DELETE | 회사 관리 (CRUD) | ✅ | +| `/api/admin/departments` | GET | 부서 목록 조회 | ✅ | + +### 2.2 테이블 및 데이터 관리 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/table-management/tables` | GET | 테이블 목록 조회 | ✅ | +| `/api/table-management/columns` | GET | 컬럼 정보 조회 | ✅ | +| `/api/table-management/entity-joins` | GET/POST | 테이블 조인 설정 | ✅ | +| `/api/data/*` | GET/POST/PUT/DELETE | 동적 테이블 데이터 CRUD | ✅ | +| `/api/ddl/*` | POST | DDL 실행 (테이블 생성/수정/삭제) | ✅ (Super Admin) | + +### 2.3 화면 및 폼 관리 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/screen-management/*` | GET/POST/PUT/DELETE | 화면 메타데이터 관리 | ✅ | +| `/api/screen-groups/*` | GET/POST/PUT/DELETE | 화면 그룹 관리 | ✅ | +| `/api/dynamic-form/*` | GET/POST | 동적 폼 생성 및 렌더링 | ✅ | +| `/api/admin/web-types` | GET/POST | 웹 컴포넌트 타입 표준 관리 | ✅ | +| `/api/admin/button-actions` | GET/POST | 버튼 액션 표준 관리 | ✅ | +| `/api/admin/template-standards` | GET/POST | 템플릿 표준 관리 | ✅ | +| `/api/admin/component-standards` | GET/POST | 컴포넌트 표준 관리 | ✅ | + +### 2.4 플로우 및 데이터플로우 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/flow/definitions` | GET/POST/PUT/DELETE | 플로우 정의 관리 | ✅ | +| `/api/flow/definitions/:id/steps` | GET/POST | 플로우 단계 관리 | ✅ | +| `/api/flow/connections` | GET/POST/DELETE | 플로우 연결 관리 | ✅ | +| `/api/flow/move` | POST | 데이터 이동 실행 | ✅ | +| `/api/flow/audit/:flowId` | GET | 플로우 오딧 로그 조회 | ✅ | +| `/api/dataflow/*` | GET/POST/PUT/DELETE | 데이터플로우 관계 관리 | ✅ | +| `/api/dataflow-diagrams/*` | GET/POST/PUT/DELETE | 데이터플로우 다이어그램 | ✅ | +| `/api/dataflow/execute` | POST | 데이터플로우 실행 | ✅ | + +### 2.5 배치 관리 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/batch-configs` | GET/POST/PUT/DELETE | 배치 설정 관리 | ✅ | +| `/api/batch-configs/connections` | GET | 사용 가능한 커넥션 목록 | ✅ | +| `/api/batch-configs/:id/execute` | POST | 배치 수동 실행 | ✅ | +| `/api/batch-management/*` | GET/POST | 배치 실행 관리 | ✅ | +| `/api/batch-execution-logs` | GET | 배치 실행 이력 조회 | ✅ | + +### 2.6 외부 연동 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/external-db-connections` | GET/POST/PUT/DELETE | 외부 DB 연결 관리 | ✅ | +| `/api/external-db-connections/:id/test` | POST | 외부 DB 연결 테스트 | ✅ | +| `/api/external-rest-api-connections` | GET/POST/PUT/DELETE | 외부 REST API 연결 | ✅ | +| `/api/external-calls/*` | GET/POST | 외부 API 호출 설정 | ✅ | +| `/api/multi-connection/query` | POST | 멀티 DB 통합 쿼리 | ✅ | + +### 2.7 메일 관리 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/mail/accounts` | GET/POST/PUT/DELETE | 메일 계정 관리 | ✅ | +| `/api/mail/templates-file` | GET/POST/PUT/DELETE | 메일 템플릿 관리 | ✅ | +| `/api/mail/send` | POST | 메일 발송 (단일/대량) | ✅ | +| `/api/mail/sent` | GET | 발송 이력 조회 | ✅ | +| `/api/mail/receive` | GET | 메일 수신함 조회 | ✅ | + +### 2.8 기타 도메인 + +| 엔드포인트 | 메서드 | 역할 | 인증 | +|-----------|--------|------|------| +| `/api/dashboards/*` | GET/POST | 대시보드 관리 | ✅ | +| `/api/admin/reports/*` | GET/POST | 리포트 생성 및 조회 | ✅ | +| `/api/files/*` | POST | 파일 업로드/다운로드 | ✅ | +| `/api/delivery/*` | GET/POST | 배송/화물 관리 | ✅ | +| `/api/risk-alerts/*` | GET/POST | 리스크/알림 관리 | ✅ | +| `/api/todos/*` | GET/POST/PUT/DELETE | To-Do 관리 | ✅ | +| `/api/bookings/*` | GET/POST | 예약 관리 | ✅ | +| `/api/digital-twin/*` | GET/POST | 디지털 트윈 (야드 관제) | ✅ | +| `/api/schedule/*` | GET/POST | 스케줄 자동 생성 | ✅ | +| `/api/work-history/*` | GET | 작업 이력 조회 | ✅ | +| `/api/table-history/*` | GET | 테이블 변경 이력 조회 | ✅ | +| `/api/roles/*` | GET/POST | 권한 그룹 관리 | ✅ | +| `/api/numbering-rules/*` | GET/POST | 채번 규칙 관리 | ✅ | +| `/api/entity-search/*` | GET | 엔티티 검색 | ✅ | +| `/api/cascading-*` | GET/POST | 연쇄 드롭다운 관계 | ✅ | +| `/api/category-tree/*` | GET/POST | 카테고리 트리 | ✅ | +| `/api/vehicle/*` | GET/POST | 차량 운행 이력 | ✅ | +| `/api/tax-invoice/*` | GET/POST | 세금계산서 관리 | ✅ | + +**총 77개 라우터 파일, 200개 이상의 엔드포인트 제공** + +--- + +## 3. 인증/인가 워크플로우 + +### 3.1 인증 메커니즘 + +``` +로그인 요청 (userId, password) + ↓ +1. AuthController.login() + ↓ +2. AuthService.processLogin() + ├─ 비밀번호 검증 (BCrypt + 마스터 패스워드) + ├─ 사용자 정보 조회 (user_info 테이블) + ├─ 로그인 로그 기록 (LOGIN_ACCESS_LOG) + └─ JWT 토큰 생성 (JwtUtils.generateToken) + ↓ +3. JWT 토큰 응답 + ├─ accessToken (24시간 유효) + ├─ refreshToken (7일 유효) + └─ userInfo (userId, userName, companyCode, userType) +``` + +### 3.2 JWT 토큰 구조 + +```typescript +// JWT Payload +{ + userId: string; // 사용자 ID + userName: string; // 사용자 이름 + companyCode: string; // 회사 코드 (멀티테넌시 핵심) + userType: string; // 사용자 유형 (SUPER_ADMIN, COMPANY_ADMIN, USER) + userLang?: string; // 사용자 언어 + iat: number; // 발급 시간 + exp: number; // 만료 시간 +} +``` + +### 3.3 미들웨어 체인 + +``` +1. refreshTokenIfNeeded (자동 토큰 갱신) + ↓ +2. authenticateToken (JWT 검증 및 사용자 정보 설정) + ↓ +3. 권한 미들웨어 (선택적) + ├─ requireSuperAdmin (회사코드 '*' 필수) + ├─ requireAdmin (회사관리자 이상) + ├─ requireCompanyAccess (회사 데이터 접근 권한) + ├─ requireDDLPermission (DDL 실행 권한) + └─ requireUserManagement (사용자 관리 권한) + ↓ +4. Controller 실행 + ↓ +5. errorHandler (에러 발생 시) +``` + +### 3.4 권한 레벨 (3단계) + +| 레벨 | companyCode | userType | 권한 범위 | +|------|-------------|----------|----------| +| **Super Admin** | `*` | `SUPER_ADMIN` | 전체 시스템 접근, DDL 실행, 회사 생성/삭제 | +| **Company Admin** | 회사코드 | `COMPANY_ADMIN` | 자사 데이터 관리, 사용자 관리, 설정 변경 | +| **일반 사용자** | 회사코드 | `USER` | 자사 데이터 조회/수정 (권한 범위 내) | + +### 3.5 토큰 갱신 전략 + +- **자동 갱신**: 토큰이 1시간 이내 만료 시 응답 헤더(`X-New-Token`)에 새 토큰 포함 +- **명시적 갱신**: `/api/auth/refresh` 엔드포인트 호출 +- **만료 처리**: 만료된 토큰은 401 Unauthorized 응답 (`TOKEN_EXPIRED`) + +--- + +## 4. 비즈니스 도메인별 모듈 분류 + +### 4.1 관리자 영역 (Admin) + +**파일**: +- `adminController.ts`, `adminService.ts`, `adminRoutes.ts` + +**주요 기능**: +- 메뉴 관리 (CRUD, 복사, 상태 토글, 일괄 삭제) +- 사용자 관리 (등록, 수정, 상태 변경, 비밀번호 초기화) +- 회사 관리 (등록, 수정, 삭제, 조회) +- 부서 관리 (조회, 사용자-부서 통합 저장) +- 로케일 설정 (다국어 지원) +- 테이블 스키마 조회 (엑셀 매핑용) + +**특징**: +- 멀티테넌시 기반 회사별 데이터 격리 +- Super Admin만 회사 생성/삭제 가능 +- 사용자 변경 이력 추적 + +### 4.2 테이블 및 데이터 관리 (Table Management & Data) + +**파일**: +- `tableManagementController.ts`, `tableManagementService.ts` +- `dataController.ts`, `dataService.ts` +- `entityJoinController.ts`, `entityJoinService.ts` + +**주요 기능**: +- 테이블 목록 조회 (PostgreSQL information_schema 활용) +- 컬럼 정보 조회 (타입, 라벨, 제약조건, 참조 관계) +- 동적 테이블 데이터 CRUD (Raw Query 기반) +- 테이블 조인 설정 및 실행 (1:N, N:M 관계) +- 컬럼 라벨 및 설정 관리 (table_type_columns) + +**특징**: +- 캐시 기반 성능 최적화 (테이블/컬럼 정보) +- 멀티테넌시 자동 필터링 (`company_code` 조건) +- 코드 타입 컬럼 자동 처리 (공통 코드 연동) + +### 4.3 화면 관리 (Screen Management) + +**파일**: +- `screenManagementController.ts`, `screenManagementService.ts` +- `screenGroupController.ts`, `screenEmbeddingController.ts` + +**주요 기능**: +- 화면 메타데이터 관리 (테이블 연결, 컬럼 설정, 레이아웃) +- 화면 그룹 관리 (폴더 구조) +- 화면 임베딩 (부모-자식 화면 데이터 전달) +- 동적 폼 생성 (JSON 기반 폼 설정 → React 컴포넌트) + +**특징**: +- Low-Code 화면 구성 +- 웹 컴포넌트 타입 표준 기반 렌더링 +- 버튼 액션 표준 지원 (저장, 삭제, 조회, 커스텀) + +### 4.4 플로우 관리 (Flow Management) + +**파일**: +- `flowController.ts`, `flowService.ts` +- `flowExecutionService.ts`, `flowStepService.ts` +- `flowConnectionService.ts`, `flowDataMoveService.ts` + +**주요 기능**: +- 플로우 정의 관리 (작업 흐름 설계) +- 플로우 단계 관리 (스텝 생성, 수정, 삭제) +- 플로우 연결 관리 (스텝 간 조건부 연결) +- 데이터 이동 실행 (스텝 간 데이터 이동) +- 오딧 로그 조회 (변경 이력 추적) + +**특징**: +- 비주얼 워크플로우 엔진 +- 조건부 분기 지원 +- 배치 데이터 이동 지원 + +### 4.5 데이터플로우 (Dataflow) + +**파일**: +- `dataflowController.ts`, `dataflowService.ts` +- `dataflowDiagramController.ts`, `dataflowDiagramService.ts` +- `dataflowExecutionController.ts` + +**주요 기능**: +- 테이블 관계 정의 (1:1, 1:N, N:M) +- 데이터플로우 다이어그램 생성 (ERD 같은 시각화) +- 데이터플로우 실행 (자동 데이터 동기화) +- 관계 기반 데이터 조회 (조인 쿼리 자동 생성) + +**특징**: +- 그래프 기반 데이터 관계 모델링 +- 다이어그램별 관계 그룹화 + +### 4.6 배치 관리 (Batch Management) + +**파일**: +- `batchController.ts`, `batchService.ts` +- `batchSchedulerService.ts`, `batchExecutionLogService.ts` +- `batchExternalDbService.ts` + +**주요 기능**: +- 배치 설정 관리 (CRUD) +- Cron 기반 스케줄링 (node-cron) +- 배치 수동/자동 실행 +- 실행 이력 조회 (성공/실패 로그) +- 외부 DB 연동 배치 지원 + +**특징**: +- 실시간 스케줄러 업데이트 +- 다중 DB 간 데이터 동기화 +- 실행 시간 제한 및 오류 알림 + +### 4.7 외부 연동 (External Integration) + +**파일**: +- `externalDbConnectionController.ts`, `externalDbConnectionService.ts` +- `externalRestApiConnectionController.ts`, `externalRestApiConnectionService.ts` +- `externalCallController.ts`, `externalCallService.ts` +- `multiConnectionQueryService.ts` + +**주요 기능**: +- 외부 DB 연결 관리 (PostgreSQL, MySQL, MSSQL, Oracle, MariaDB) +- 외부 REST API 연결 관리 +- 멀티 DB 통합 쿼리 실행 +- 연결 테스트 및 상태 확인 +- 크레덴셜 암호화 저장 + +**특징**: +- 5종 DB 지원 (PostgreSQL, MySQL, MSSQL, Oracle, MariaDB) +- Connection Pool 기반 연결 관리 +- 비밀번호 암호화 (AES-256-CBC) + +### 4.8 메일 관리 (Mail Management) + +**파일**: +- `mailAccountFileController.ts`, `mailAccountFileService.ts` +- `mailTemplateFileController.ts`, `mailTemplateFileService.ts` +- `mailSendSimpleController.ts`, `mailSendSimpleService.ts` +- `mailSentHistoryController.ts`, `mailSentHistoryService.ts` +- `mailReceiveBasicController.ts`, `mailReceiveBasicService.ts` + +**주요 기능**: +- 메일 계정 관리 (SMTP/IMAP 설정) +- 메일 템플릿 관리 (JSON 기반 컴포넌트 조합) +- 메일 발송 (단일/대량, 첨부파일 지원) +- 발송 이력 조회 (30일 자동 삭제) +- 메일 수신함 조회 (IMAP) + +**특징**: +- Nodemailer 기반 발송 +- 템플릿 변수 치환 +- 대량 발송 지원 (100건/배치) +- 메일 예약 발송 + +### 4.9 대시보드 (Dashboard) + +**파일**: +- `DashboardController.ts`, `DashboardService.ts` + +**주요 기능**: +- 대시보드 위젯 관리 (차트, 테이블, 카드) +- 실시간 데이터 조회 (집계 쿼리) +- 사용자별 대시보드 설정 + +**특징**: +- JSON 기반 위젯 설정 +- 캐시 기반 성능 최적화 + +### 4.10 기타 도메인 + +**파일 및 주요 기능**: +- **리포트**: 리포트 생성 및 조회 (`reportController.ts`, `reportService.ts`) +- **파일**: 파일 업로드/다운로드 (`fileController.ts`, Multer) +- **배송/화물**: 배송 관리, 화물 추적 (`deliveryController.ts`) +- **리스크/알림**: 리스크 알림, 캐시 기반 자동 갱신 (`riskAlertController.ts`) +- **To-Do**: 할 일 관리 (`todoController.ts`) +- **예약**: 예약 요청 관리 (`bookingController.ts`) +- **디지털 트윈**: 야드 관제, 3D 레이아웃 (`digitalTwinController.ts`) +- **스케줄**: 스케줄 자동 생성 (`scheduleController.ts`) +- **작업 이력**: 작업 로그 조회 (`workHistoryController.ts`) +- **권한 그룹**: 권한 그룹 관리 (`roleController.ts`) +- **채번 규칙**: 자동 채번 규칙 (`numberingRuleController.ts`) +- **엔티티 검색**: 동적 엔티티 검색 (`entitySearchController.ts`) +- **연쇄 드롭다운**: 조건부 드롭다운 (`cascadingController.ts` 시리즈) + +--- + +## 5. 미들웨어 스택 구성 + +### 5.1 미들웨어 실행 순서 (app.ts 기준) + +``` +1. 프로세스 레벨 예외 처리 (unhandledRejection, uncaughtException) +2. 보안 헤더 (helmet) +3. 압축 (compression) +4. 바디 파싱 (express.json, express.urlencoded) +5. 정적 파일 서빙 (/uploads) +6. CORS (cors) +7. Rate Limiting (express-rate-limit) +8. 토큰 자동 갱신 (refreshTokenIfNeeded) +9. [라우트별 미들웨어] + ├─ authenticateToken (모든 /api/* 라우트) + ├─ 권한 미들웨어 (선택적) + └─ 컨트롤러 실행 +10. 404 핸들러 +11. 에러 핸들러 (errorHandler) +``` + +### 5.2 미들웨어 파일 + +#### `authMiddleware.ts` +- **authenticateToken**: JWT 토큰 검증 및 사용자 정보 설정 +- **optionalAuth**: 선택적 인증 (토큰 없어도 통과) +- **requireAdmin**: 관리자 권한 필수 (userId === 'plm_admin') +- **refreshTokenIfNeeded**: 토큰 자동 갱신 (1시간 이내 만료 시) +- **checkAuthStatus**: 인증 상태 확인 (유효성 검사만) + +#### `permissionMiddleware.ts` +- **requireSuperAdmin**: 슈퍼관리자 권한 필수 (companyCode === '*') +- **requireAdmin**: 관리자 이상 권한 필수 (Super Admin + Company Admin) +- **requireCompanyAccess**: 회사 데이터 접근 권한 체크 +- **requireUserManagement**: 사용자 관리 권한 체크 +- **requireCompanySettingsManagement**: 회사 설정 변경 권한 체크 +- **requireCompanyManagement**: 회사 생성/삭제 권한 체크 +- **requireDDLPermission**: DDL 실행 권한 체크 + +#### `superAdminMiddleware.ts` +- **requireSuperAdmin**: 슈퍼관리자 권한 확인 (DDL 전용) +- **validateDDLPermission**: DDL 실행 전 추가 보안 검증 (5초 간격 제한) +- **isSuperAdmin**: 슈퍼관리자 여부 확인 유틸 함수 +- **checkDDLPermission**: DDL 권한 체크 (미들웨어 없이 사용) + +#### `errorHandler.ts` +- **AppError**: 커스텀 에러 클래스 (statusCode, isOperational) +- **errorHandler**: 전역 에러 핸들러 (PostgreSQL, JWT 에러 처리) +- **notFoundHandler**: 404 에러 핸들러 + +### 5.3 보안 설정 + +```typescript +// helmet: 보안 헤더 설정 +helmet({ + contentSecurityPolicy: { + directives: { + 'frame-ancestors': ['self', 'http://localhost:9771', 'http://localhost:3000'] + } + } +}) + +// Rate Limiting: 1분당 10,000 요청 (개발), 100 요청 (운영) +rateLimit({ + windowMs: 1 * 60 * 1000, + max: process.env.NODE_ENV === 'development' ? 10000 : 100, + skip: (req) => { + // 헬스 체크, 자주 호출되는 API는 제외 + return req.path === '/health' + || req.path.includes('/table-management/') + || req.path.includes('/external-db-connections/') + } +}) + +// CORS: 환경별 origin 설정 +cors({ + origin: process.env.NODE_ENV === 'development' + ? true + : ['http://localhost:9771', 'http://39.117.244.52:5555'], + credentials: true +}) +``` + +--- + +## 6. 서비스 레이어 패턴 + +### 6.1 서비스 레이어 구조 + +``` +Controller (요청 처리) + ↓ +Service (비즈니스 로직) + ↓ +Database (Raw Query 실행) + ↓ +PostgreSQL (데이터 저장소) +``` + +### 6.2 데이터베이스 접근 방식 + +#### `db.ts` - Raw Query 매니저 + +```typescript +// 기본 쿼리 실행 +async function query(text: string, params?: any[]): Promise + +// 단일 행 조회 +async function queryOne(text: string, params?: any[]): Promise + +// 트랜잭션 +async function transaction(callback: (client: PoolClient) => Promise): Promise + +// 연결 풀 상태 +function getPoolStatus(): { totalCount, idleCount, waitingCount } +``` + +#### Connection Pool 설정 + +```typescript +new Pool({ + host: dbConfig.host, + port: dbConfig.port, + database: dbConfig.database, + user: dbConfig.user, + password: dbConfig.password, + + // 연결 풀 설정 + min: process.env.NODE_ENV === 'production' ? 5 : 2, + max: process.env.NODE_ENV === 'production' ? 20 : 10, + + // 타임아웃 설정 + connectionTimeoutMillis: 30000, // 30초 + idleTimeoutMillis: 600000, // 10분 + statement_timeout: 60000, // 60초 + query_timeout: 60000, + + application_name: 'WACE-PLM-Backend' +}) +``` + +### 6.3 서비스 패턴 예시 + +#### 멀티테넌시 쿼리 패턴 + +```typescript +// Super Admin: 모든 데이터 조회 +if (companyCode === '*') { + query = 'SELECT * FROM table_name ORDER BY company_code'; + params = []; +} +// 일반 사용자: 자사 데이터만 조회 (Super Admin 데이터 제외) +else { + query = ` + SELECT * FROM table_name + WHERE company_code = $1 AND company_code != '*' + ORDER BY created_at DESC + `; + params = [companyCode]; +} +``` + +#### 트랜잭션 패턴 + +```typescript +await transaction(async (client) => { + // 1. 부모 레코드 삽입 + const parent = await client.query( + 'INSERT INTO parent_table (...) VALUES (...) RETURNING *', + [...] + ); + + // 2. 자식 레코드 삽입 + await client.query( + 'INSERT INTO child_table (parent_id, ...) VALUES ($1, ...) RETURNING *', + [parent.rows[0].id, ...] + ); + + return { success: true }; +}); +``` + +#### 캐시 패턴 + +```typescript +// 캐시 조회 +const cachedData = cache.get(CacheKeys.TABLE_LIST); +if (cachedData) { + return cachedData; +} + +// DB 조회 +const data = await query('SELECT ...'); + +// 캐시 저장 (10분 TTL) +cache.set(CacheKeys.TABLE_LIST, data, 10 * 60 * 1000); + +return data; +``` + +### 6.4 외부 DB 커넥터 패턴 + +```typescript +// DatabaseConnectorFactory.ts +export class DatabaseConnectorFactory { + static createConnector(dbType: string, config: ConnectionConfig): DatabaseConnector { + 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); + case 'mariadb': return new MariaDBConnector(config); + default: throw new Error(`Unsupported DB type: ${dbType}`); + } + } +} + +// 사용 예시 +const connector = DatabaseConnectorFactory.createConnector('mysql', config); +await connector.connect(); +const result = await connector.executeQuery('SELECT * FROM users'); +await connector.disconnect(); +``` + +--- + +## 7. 멀티테넌시 구현 방식 + +### 7.1 핵심 원칙 + +**CRITICAL PROJECT RULES**: +1. **모든 쿼리는 company_code 필터 필수** +2. **req.user!.companyCode 사용 (클라이언트 전송 값 신뢰 금지)** +3. **Super Admin (company_code = '*')만 전체 데이터 조회** +4. **일반 사용자는 company_code = '*' 데이터 조회 불가** + +### 7.2 쿼리 패턴 + +```typescript +const companyCode = req.user!.companyCode; + +// Super Admin: 모든 회사 데이터 조회 +if (companyCode === '*') { + query = 'SELECT * FROM users ORDER BY company_code'; + params = []; +} +// 일반 사용자: 자사 데이터만 조회 (Super Admin 제외) +else { + query = ` + SELECT * FROM users + WHERE company_code = $1 AND company_code != '*' + `; + params = [companyCode]; +} +``` + +### 7.3 테이블 설계 + +```sql +-- 모든 비즈니스 테이블에 company_code 컬럼 필수 +CREATE TABLE table_name ( + id SERIAL PRIMARY KEY, + company_code VARCHAR(50) NOT NULL, -- 회사 코드 + ... + INDEX idx_company_code (company_code) +); + +-- Super Admin 데이터: company_code = '*' +-- 회사별 데이터: company_code = '회사코드' +``` + +### 7.4 회사 전환 (Super Admin 전용) + +```typescript +// POST /api/auth/switch-company +{ + targetCompanyCode: "ILSHIN" // 전환할 회사 코드 +} + +// 응답 +{ + success: true, + token: "새로운 JWT 토큰", // companyCode가 변경된 토큰 + userInfo: { companyCode: "ILSHIN", ... } +} +``` + +### 7.5 권한 체크 + +```typescript +// 회사 데이터 접근 권한 확인 +export function canAccessCompanyData(user: PersonBean, targetCompanyCode: string): boolean { + // Super Admin: 모든 회사 접근 가능 + if (user.companyCode === '*') { + return true; + } + + // 일반 사용자: 자사만 접근 가능 + return user.companyCode === targetCompanyCode; +} +``` + +--- + +## 8. 에러 핸들링 전략 + +### 8.1 에러 핸들링 구조 + +``` +Controller (try-catch) + ↓ 에러 발생 +Service (throw error) + ↓ +errorHandler (미들웨어) + ├─ PostgreSQL 에러 처리 + ├─ JWT 에러 처리 + ├─ 커스텀 에러 처리 (AppError) + └─ 응답 전송 (JSON) +``` + +### 8.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; + } +} + +// 사용 예시 +throw new AppError('중복된 데이터가 존재합니다.', 400); +``` + +### 8.3 PostgreSQL 에러 처리 + +```typescript +// errorHandler.ts +if (pgError.code === '23505') { // unique_violation + error = new AppError('중복된 데이터가 존재합니다.', 400); +} else if (pgError.code === '23503') { // foreign_key_violation + error = new AppError('참조 무결성 제약 조건 위반입니다.', 400); +} else if (pgError.code === '23502') { // not_null_violation + error = new AppError('필수 입력값이 누락되었습니다.', 400); +} +``` + +### 8.4 에러 응답 형식 + +```json +{ + "success": false, + "error": { + "code": "UNIQUE_VIOLATION", + "message": "중복된 데이터가 존재합니다.", + "details": "사용자 ID가 이미 존재합니다.", + "stack": "..." // 개발 환경에서만 포함 + } +} +``` + +### 8.5 에러 로깅 + +```typescript +// logger.ts (Winston 기반) +logger.error({ + message: error.message, + stack: error.stack, + url: req.url, + method: req.method, + ip: req.ip, + userAgent: req.get('User-Agent') +}); +``` + +### 8.6 프로세스 레벨 예외 처리 + +```typescript +// app.ts +process.on('unhandledRejection', (reason, promise) => { + logger.error('⚠️ Unhandled Promise Rejection:', reason); + // 프로세스 종료하지 않고 로깅만 수행 +}); + +process.on('uncaughtException', (error) => { + logger.error('🔥 Uncaught Exception:', error); + // 심각한 에러 시 graceful shutdown 고려 +}); +``` + +--- + +## 9. 파일 업로드/다운로드 처리 + +### 9.1 파일 업로드 + +**파일**: `fileController.ts`, `fileRoutes.ts` + +```typescript +// Multer 설정 +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, 'uploads/'); + }, + filename: (req, file, cb) => { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); + } +}); + +const upload = multer({ + storage, + limits: { fileSize: 10 * 1024 * 1024 }, // 10MB + fileFilter: (req, file, cb) => { + // 허용된 확장자 체크 + const allowedTypes = /jpeg|jpg|png|gif|pdf|doc|docx|xls|xlsx/; + const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); + const mimetype = allowedTypes.test(file.mimetype); + + if (mimetype && extname) { + return cb(null, true); + } else { + cb(new Error('허용되지 않는 파일 형식입니다.')); + } + } +}); + +// 라우트 +router.post('/upload', authenticateToken, upload.single('file'), uploadFile); +router.post('/upload-multiple', authenticateToken, upload.array('files', 10), uploadMultipleFiles); +``` + +### 9.2 파일 다운로드 + +```typescript +// 정적 파일 서빙 (app.ts) +app.use('/uploads', + (req, res, next) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'); + res.setHeader('Cache-Control', 'public, max-age=3600'); + next(); + }, + express.static(path.join(process.cwd(), 'uploads')) +); + +// 다운로드 엔드포인트 +router.get('/download/:filename', authenticateToken, async (req, res) => { + const filename = req.params.filename; + const filePath = path.join(process.cwd(), 'uploads', filename); + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ error: '파일을 찾을 수 없습니다.' }); + } + + res.download(filePath); +}); +``` + +### 9.3 화면별 파일 관리 + +**파일**: `screenFileController.ts`, `screenFileService.ts` + +```typescript +// 화면별 파일 업로드 +router.post('/screens/:screenId/files', authenticateToken, upload.single('file'), uploadScreenFile); + +// 화면별 파일 목록 조회 +router.get('/screens/:screenId/files', authenticateToken, getScreenFiles); + +// 파일 삭제 +router.delete('/screens/:screenId/files/:fileId', authenticateToken, deleteScreenFile); +``` + +--- + +## 10. 외부 연동 + +### 10.1 외부 DB 연결 + +**지원 DB**: PostgreSQL, MySQL, MSSQL, Oracle, MariaDB + +**파일**: +- `externalDbConnectionService.ts` +- `PostgreSQLConnector.ts`, `MySQLConnector.ts`, `MSSQLConnector.ts`, `OracleConnector.ts`, `MariaDBConnector.ts` + +```typescript +// 외부 DB 연결 설정 +{ + connection_name: "외부 ERP DB", + db_type: "mysql", + host: "192.168.0.100", + port: 3306, + database: "erp_db", + username: "erp_user", + password: "encrypted_password", // AES-256-CBC 암호화 + company_code: "ILSHIN", + is_active: "Y" +} + +// 연결 테스트 +POST /api/external-db-connections/:id/test +{ + success: true, + message: "연결 테스트 성공" +} + +// 쿼리 실행 +POST /api/external-db-connections/:id/query +{ + query: "SELECT * FROM products WHERE category = ?", + params: ["전자제품"] +} +``` + +### 10.2 외부 REST API 연결 + +**파일**: `externalRestApiConnectionService.ts` + +```typescript +// 외부 REST API 연결 설정 +{ + connection_name: "날씨 API", + base_url: "https://api.weather.com", + auth_type: "bearer", // bearer, api-key, basic, oauth2 + auth_credentials: { + token: "encrypted_token" + }, + headers: { + "Content-Type": "application/json" + }, + company_code: "ILSHIN" +} + +// API 호출 +POST /api/external-rest-api-connections/:id/call +{ + method: "GET", + endpoint: "/weather", + params: { city: "Seoul" } +} +``` + +### 10.3 멀티 DB 통합 쿼리 + +**파일**: `multiConnectionQueryService.ts` + +```typescript +// 여러 DB에서 동시 쿼리 실행 +POST /api/multi-connection/query +{ + connections: [ + { + connectionId: 1, + query: "SELECT * FROM orders WHERE status = 'pending'" + }, + { + connectionId: 2, + query: "SELECT * FROM inventory WHERE quantity < 10" + } + ] +} + +// 응답 +{ + success: true, + results: [ + { connectionId: 1, data: [...], rowCount: 15 }, + { connectionId: 2, data: [...], rowCount: 8 } + ] +} +``` + +### 10.4 Open API Proxy + +**파일**: `openApiProxyController.ts` + +```typescript +// 날씨 API +GET /api/open-api/weather?city=Seoul + +// 환율 API +GET /api/open-api/exchange-rate?from=USD&to=KRW +``` + +--- + +## 11. 배치/스케줄 처리 + +### 11.1 배치 스케줄러 + +**파일**: `batchSchedulerService.ts` + +```typescript +// 배치 설정 +{ + batch_name: "일일 재고 동기화", + batch_type: "external_db", // external_db, rest_api, internal + cron_schedule: "0 2 * * *", // 매일 새벽 2시 + source_connection_id: 1, + source_query: "SELECT * FROM inventory", + target_table: "inventory_sync", + mapping: { + product_id: "item_id", + quantity: "stock_qty" + }, + is_active: "Y" +} + +// 스케줄러 초기화 (서버 시작 시) +await BatchSchedulerService.initializeScheduler(); + +// 배치 수동 실행 +POST /api/batch-configs/:id/execute +``` + +### 11.2 Cron 기반 자동 실행 + +```typescript +// node-cron 사용 +const task = cron.schedule( + config.cron_schedule, // "0 2 * * *" + async () => { + logger.info(`배치 실행 시작: ${config.batch_name}`); + await executeBatchConfig(config); + }, + { timezone: 'Asia/Seoul' } +); + +// 스케줄 업데이트 +await BatchSchedulerService.updateBatchSchedule(configId); + +// 스케줄 제거 +await BatchSchedulerService.removeBatchSchedule(configId); +``` + +### 11.3 배치 실행 로그 + +**파일**: `batchExecutionLogService.ts` + +```typescript +// 배치 실행 이력 +{ + batch_config_id: 1, + execution_status: "success", // success, failed, running + start_time: "2024-12-24T02:00:00Z", + end_time: "2024-12-24T02:05:23Z", + rows_processed: 1523, + rows_inserted: 1200, + rows_updated: 300, + rows_failed: 23, + error_message: null, + execution_log: "..." +} + +// 배치 이력 조회 +GET /api/batch-execution-logs?batch_config_id=1&page=1&limit=10 +``` + +### 11.4 자동 스케줄 작업 + +**파일**: `app.ts` + +```typescript +// 메일 자동 삭제 (매일 새벽 2시) +cron.schedule('0 2 * * *', async () => { + logger.info('🗑️ 30일 지난 삭제된 메일 자동 삭제 시작...'); + const deletedCount = await mailSentHistoryService.cleanupOldDeletedMails(); + logger.info(`✅ 30일 지난 메일 ${deletedCount}개 자동 삭제 완료`); +}); + +// 리스크/알림 자동 갱신 (10분 간격) +const cacheService = RiskAlertCacheService.getInstance(); +cacheService.startAutoRefresh(); +``` + +--- + +## 12. 컨트롤러/서비스 상세 역할 + +### 12.1 인증 및 관리자 + +#### `authController.ts` / `authService.ts` +- **로그인**: 비밀번호 검증, JWT 토큰 발급, 로그인 로그 기록 +- **회원가입**: 공차중계 사용자 등록 +- **토큰 갱신**: accessToken, refreshToken 갱신 +- **현재 사용자 정보**: JWT 기반 사용자 정보 조회 +- **로그아웃**: 토큰 무효화 (클라이언트 측 처리) +- **회사 전환**: Super Admin 전용 회사 전환 + +#### `adminController.ts` / `adminService.ts` +- **메뉴 관리**: 메뉴 트리 조회, 메뉴 CRUD, 메뉴 복사, 상태 토글, 일괄 삭제 +- **사용자 관리**: 사용자 목록, 사용자 CRUD, 상태 변경, 비밀번호 초기화, 변경 이력 +- **회사 관리**: 회사 목록, 회사 CRUD (Super Admin 전용) +- **부서 관리**: 부서 목록, 사용자-부서 통합 저장 +- **로케일 설정**: 사용자 언어 설정 (ko, en) +- **테이블 스키마**: 엑셀 업로드 컬럼 매핑용 스키마 조회 + +### 12.2 테이블 및 데이터 + +#### `tableManagementController.ts` / `tableManagementService.ts` +- **테이블 목록**: PostgreSQL information_schema 조회 +- **컬럼 정보**: 컬럼 타입, 제약조건, 라벨, 참조 관계 +- **컬럼 라벨 관리**: 다국어 라벨, 표시 순서, 표시 여부 +- **컬럼 설정**: 입력 타입, 코드 카테고리, 필수 여부, 기본값 +- **테이블 조회 설정**: 조회 컬럼, 정렬 순서, 필터 조건 +- **캐시 관리**: 테이블/컬럼 정보 캐시 (10분 TTL) + +#### `dataController.ts` / `dataService.ts` +- **동적 데이터 조회**: 테이블명 기반 데이터 조회 (페이지네이션, 필터, 정렬) +- **동적 데이터 생성**: INSERT 쿼리 자동 생성 +- **동적 데이터 수정**: UPDATE 쿼리 자동 생성 +- **동적 데이터 삭제**: DELETE 쿼리 자동 생성 (논리 삭제 지원) +- **멀티테넌시 자동 필터링**: company_code 자동 추가 + +#### `entityJoinController.ts` / `entityJoinService.ts` +- **조인 설정 관리**: 테이블 간 조인 관계 설정 (1:N, N:M) +- **조인 쿼리 실행**: 설정된 조인 관계 기반 데이터 조회 +- **참조 데이터 캐싱**: 자주 사용되는 참조 데이터 캐시 + +### 12.3 화면 관리 + +#### `screenManagementController.ts` / `screenManagementService.ts` +- **화면 메타데이터 관리**: 화면 설정 (테이블, 컬럼, 레이아웃) +- **화면 목록 조회**: 회사별, 화면 그룹별 필터링 +- **화면 복사**: 화면 설정 복제 +- **화면 삭제**: 논리 삭제 +- **화면 설정 조회**: 화면 메타데이터 상세 조회 + +#### `dynamicFormController.ts` / `dynamicFormService.ts` +- **동적 폼 생성**: JSON 기반 폼 설정 → React 컴포넌트 +- **폼 유효성 검사**: 필수 입력, 데이터 타입, 길이 제한 +- **폼 제출**: 데이터 저장 (INSERT/UPDATE) + +#### `buttonActionStandardController.ts` / `buttonActionStandardService.ts` +- **버튼 액션 표준**: 저장, 삭제, 조회, 엑셀 다운로드, 커스텀 액션 +- **버튼 액션 설정**: 액션 타입, 파라미터, 권한 설정 + +### 12.4 플로우 및 데이터플로우 + +#### `flowController.ts` / `flowService.ts` +- **플로우 정의**: 플로우 생성, 수정, 삭제, 조회 +- **플로우 단계**: 단계 생성, 수정, 삭제, 순서 변경 +- **플로우 연결**: 단계 간 연결 (조건부 분기) +- **데이터 이동**: 단계 간 데이터 이동 (단일/배치) +- **오딧 로그**: 플로우 실행 이력 조회 + +#### `dataflowController.ts` / `dataflowService.ts` +- **테이블 관계 정의**: 테이블 간 관계 설정 (1:1, 1:N, N:M) +- **데이터플로우 다이어그램**: ERD 같은 시각화 +- **데이터플로우 실행**: 관계 기반 데이터 동기화 +- **관계 조회**: 다이어그램별 관계 목록 + +### 12.5 배치 관리 + +#### `batchController.ts` / `batchService.ts` +- **배치 설정 관리**: 배치 CRUD +- **배치 실행**: 수동 실행, 스케줄 실행 +- **배치 이력**: 실행 로그, 성공/실패 통계 +- **커넥션 조회**: 사용 가능한 외부 DB/API 목록 + +#### `batchSchedulerService.ts` +- **스케줄러 초기화**: 서버 시작 시 활성 배치 등록 +- **스케줄 등록**: Cron 표현식 기반 스케줄 등록 +- **스케줄 업데이트**: 배치 설정 변경 시 스케줄 재등록 +- **스케줄 제거**: 배치 삭제 시 스케줄 제거 + +#### `batchExternalDbService.ts` +- **외부 DB 배치**: 외부 DB → 내부 DB 데이터 동기화 +- **컬럼 매핑**: 소스-타겟 컬럼 매핑 +- **데이터 변환**: 타입 변환, 값 변환 + +### 12.6 외부 연동 + +#### `externalDbConnectionController.ts` / `externalDbConnectionService.ts` +- **외부 DB 연결 관리**: 연결 CRUD +- **연결 테스트**: 연결 유효성 검증 +- **쿼리 실행**: 외부 DB 쿼리 실행 +- **테이블 목록**: 외부 DB 테이블 목록 조회 +- **컬럼 정보**: 외부 DB 컬럼 정보 조회 + +#### `externalRestApiConnectionController.ts` / `externalRestApiConnectionService.ts` +- **REST API 연결 관리**: API 연결 CRUD +- **API 호출**: 외부 API 호출 (GET, POST, PUT, DELETE) +- **인증 처리**: Bearer, API Key, Basic, OAuth2 +- **응답 캐싱**: API 응답 캐싱 (TTL 설정) + +#### `multiConnectionQueryService.ts` +- **멀티 DB 통합 쿼리**: 여러 DB에서 동시 쿼리 실행 +- **결과 병합**: 여러 DB 쿼리 결과 병합 +- **오류 처리**: 부분 실패 시 에러 로그 기록 + +### 12.7 메일 관리 + +#### `mailAccountFileController.ts` / `mailAccountFileService.ts` +- **메일 계정 관리**: SMTP/IMAP 계정 CRUD +- **계정 테스트**: 연결 유효성 검증 +- **계정 상태**: 활성/비활성 상태 관리 + +#### `mailTemplateFileController.ts` / `mailTemplateFileService.ts` +- **메일 템플릿 관리**: 템플릿 CRUD +- **템플릿 컴포넌트**: JSON 기반 컴포넌트 조합 (헤더, 본문, 버튼, 푸터) +- **변수 치환**: {변수명} 형태의 변수 치환 + +#### `mailSendSimpleController.ts` / `mailSendSimpleService.ts` +- **메일 발송**: 단일 발송, 대량 발송 (100건/배치) +- **첨부파일**: 다중 첨부파일 지원 +- **참조/숨은참조**: CC, BCC 지원 +- **발송 이력**: 발송 성공/실패 로그 기록 + +#### `mailSentHistoryController.ts` / `mailSentHistoryService.ts` +- **발송 이력 조회**: 페이지네이션, 필터링, 검색 +- **이력 삭제**: 논리 삭제 (30일 후 물리 삭제) +- **자동 삭제**: Cron 기반 자동 삭제 (매일 새벽 2시) + +#### `mailReceiveBasicController.ts` / `mailReceiveBasicService.ts` +- **메일 수신**: IMAP 기반 메일 수신 +- **메일 목록**: 수신함 목록 조회 +- **메일 읽기**: 메일 상세 조회, 첨부파일 다운로드 + +### 12.8 대시보드 및 리포트 + +#### `DashboardController.ts` / `DashboardService.ts` +- **대시보드 위젯**: 차트, 테이블, 카드 위젯 +- **실시간 데이터**: 집계 쿼리 기반 실시간 데이터 조회 +- **사용자 설정**: 사용자별 대시보드 레이아웃 저장 + +#### `reportController.ts` / `reportService.ts` +- **리포트 생성**: 동적 리포트 생성 (PDF, Excel, Word) +- **리포트 템플릿**: 템플릿 기반 리포트 생성 +- **리포트 스케줄**: 정기 리포트 자동 생성 및 메일 발송 + +### 12.9 기타 도메인 + +#### `deliveryController.ts` / `deliveryService.ts` +- **배송 관리**: 배송 정보 등록, 조회, 수정 +- **화물 추적**: 배송 상태 추적 + +#### `riskAlertController.ts` / `riskAlertService.ts` / `riskAlertCacheService.ts` +- **리스크 알림**: 리스크 기준 설정, 알림 발생 +- **자동 갱신**: 10분 간격 자동 갱신 (캐시 기반) + +#### `todoController.ts` / `todoService.ts` +- **To-Do 관리**: 할 일 CRUD, 상태 변경 (대기, 진행, 완료) + +#### `bookingController.ts` / `bookingService.ts` +- **예약 관리**: 예약 요청 CRUD, 승인/거부 + +#### `digitalTwinController.ts` / `digitalTwinLayoutController.ts` / `digitalTwinDataController.ts` +- **디지털 트윈**: 야드 관제, 3D 레이아웃, 실시간 데이터 시각화 + +#### `scheduleController.ts` / `scheduleService.ts` +- **스케줄 자동 생성**: 작업 스케줄 자동 생성 (규칙 기반) + +#### `workHistoryController.ts` / `workHistoryService.ts` +- **작업 이력**: 작업 로그 조회, 필터링, 검색 + +#### `roleController.ts` / `roleService.ts` +- **권한 그룹**: 권한 그룹 CRUD, 사용자-권한 매핑 + +#### `numberingRuleController.ts` / `numberingRuleService.ts` +- **채번 규칙**: 자동 채번 규칙 설정 (접두사, 연번, 접미사) + +#### `entitySearchController.ts` / `entitySearchService.ts` +- **엔티티 검색**: 동적 엔티티 검색 (테이블, 컬럼, 조건 기반) + +#### `cascadingController.ts` 시리즈 +- **연쇄 드롭다운**: 조건부 드롭다운 관계 설정 +- **자동 입력**: 연쇄 자동 입력 관계 설정 +- **상호 배제**: 상호 배타적 선택 관계 설정 +- **다단계 계층**: 계층 구조 관계 설정 + +--- + +## 📊 통계 요약 + +| 구분 | 개수 | +|------|------| +| **컨트롤러** | 69개 | +| **서비스** | 87개 | +| **라우터** | 77개 | +| **미들웨어** | 4개 | +| **엔드포인트** | 200개 이상 | +| **데이터베이스 커넥터** | 5종 (PostgreSQL, MySQL, MSSQL, Oracle, MariaDB) | +| **유틸리티** | 16개 | +| **타입 정의** | 26개 | + +--- + +## 🔧 기술 스택 + +```json +{ + "런타임": "Node.js 20.10.0+", + "언어": "TypeScript 5.3.3", + "프레임워크": "Express 4.18.2", + "데이터베이스": "PostgreSQL (pg 8.16.3)", + "인증": "JWT (jsonwebtoken 9.0.2)", + "암호화": "BCrypt (bcryptjs 2.4.3)", + "로깅": "Winston 3.11.0", + "스케줄링": "node-cron 4.2.1", + "메일": "Nodemailer 6.10.1 + IMAP 0.8.19", + "파일 업로드": "Multer 1.4.5", + "보안": "Helmet 7.1.0", + "외부 DB": "mysql2, mssql, oracledb", + "캐싱": "In-Memory Cache (Map 기반)", + "압축": "compression 1.7.4", + "Rate Limiting": "express-rate-limit 7.1.5" +} +``` + +--- + +## 📁 핵심 파일 경로 + +``` +backend-node/ +├── src/ +│ ├── app.ts # 앱 진입점 +│ ├── config/environment.ts # 환경변수 설정 +│ ├── database/ +│ │ ├── db.ts # Raw Query 매니저 +│ │ ├── DatabaseConnectorFactory.ts # DB 커넥터 팩토리 +│ │ └── [DB]Connector.ts # 각 DB별 커넥터 +│ ├── middleware/ +│ │ ├── authMiddleware.ts # JWT 인증 +│ │ ├── permissionMiddleware.ts # 권한 체크 +│ │ ├── superAdminMiddleware.ts # Super Admin 체크 +│ │ └── errorHandler.ts # 에러 핸들링 +│ ├── utils/ +│ │ ├── logger.ts # Winston 로거 +│ │ ├── jwtUtils.ts # JWT 유틸 +│ │ ├── encryptUtil.ts # BCrypt 암호화 +│ │ ├── passwordEncryption.ts # AES 암호화 +│ │ ├── cache.ts # 캐시 유틸 +│ │ └── permissionUtils.ts # 권한 유틸 +│ └── types/ +│ ├── auth.ts # 인증 타입 +│ ├── tableManagement.ts # 테이블 관리 타입 +│ └── ... +└── package.json # 의존성 관리 +``` + +--- + +## 🚀 서버 시작 프로세스 + +``` +1. dotenv 환경변수 로드 +2. Express 앱 생성 +3. 미들웨어 설정 (helmet, cors, compression, rate-limit) +4. 데이터베이스 연결 풀 초기화 +5. 라우터 등록 (77개 라우터) +6. 에러 핸들러 등록 +7. 서버 리스닝 (기본 포트: 8080) +8. 데이터베이스 마이그레이션 실행 +9. 배치 스케줄러 초기화 +10. 리스크/알림 자동 갱신 시작 +11. 메일 자동 삭제 스케줄러 시작 +``` + +--- + +## 🔒 보안 고려사항 + +1. **JWT 기반 인증**: 세션 없이 무상태(Stateless) 인증 +2. **비밀번호 암호화**: BCrypt (12 rounds) +3. **외부 DB 크레덴셜 암호화**: AES-256-CBC +4. **SQL Injection 방지**: Parameterized Query 필수 +5. **XSS 방지**: Helmet 보안 헤더 +6. **CSRF 방지**: CORS 설정 + JWT +7. **Rate Limiting**: 1분당 요청 수 제한 +8. **DDL 실행 제한**: Super Admin만 가능 + 5초 간격 제한 +9. **멀티테넌시 격리**: company_code 자동 필터링 +10. **에러 정보 노출 방지**: 운영 환경에서 스택 트레이스 숨김 + +--- + +## 📝 추천 개선 사항 + +1. **API 문서화**: Swagger/OpenAPI 자동 생성 +2. **단위 테스트**: Jest 기반 테스트 커버리지 확대 +3. **Redis 캐싱**: In-Memory 캐시 → Redis 전환 +4. **로그 중앙화**: Winston → ELK Stack 연동 +5. **성능 모니터링**: APM 도구 연동 (New Relic, Datadog) +6. **Docker 컨테이너화**: Dockerfile 및 docker-compose 개선 +7. **CI/CD 파이프라인**: GitHub Actions, Jenkins 연동 +8. **API Rate Limiting 세분화**: 엔드포인트별 제한 설정 +9. **WebSocket 지원**: 실시간 알림 및 데이터 업데이트 +10. **GraphQL API**: REST API + GraphQL 병행 지원 + +--- + +**문서 작성자**: WACE 백엔드 전문가 +**최종 수정일**: 2026-02-06 +**버전**: 1.0.0 + diff --git a/docs/frontend-architecture-analysis.md b/docs/frontend-architecture-analysis.md new file mode 100644 index 00000000..fb367585 --- /dev/null +++ b/docs/frontend-architecture-analysis.md @@ -0,0 +1,1920 @@ +# WACE ERP 프론트엔드 아키텍처 상세 분석 + +> 작성일: 2026-02-06 +> 작성자: AI Assistant +> 프로젝트: WACE ERP-node +> 목적: 시스템 전체 워크플로우 문서화를 위한 프론트엔드 구조 분석 + +--- + +## 목차 +1. [전체 디렉토리 구조](#1-전체-디렉토리-구조) +2. [Next.js App Router 구조](#2-nextjs-app-router-구조) +3. [컴포넌트 시스템](#3-컴포넌트-시스템) +4. [V2 컴포넌트 시스템](#4-v2-컴포넌트-시스템) +5. [화면 디자이너 워크플로우](#5-화면-디자이너-워크플로우) +6. [API 클라이언트 시스템](#6-api-클라이언트-시스템) +7. [상태 관리](#7-상태-관리) +8. [레지스트리 시스템](#8-레지스트리-시스템) +9. [대시보드 시스템](#9-대시보드-시스템) +10. [다국어 지원](#10-다국어-지원) +11. [인증 플로우](#11-인증-플로우) +12. [사용자 워크플로우](#12-사용자-워크플로우) + +--- + +## 1. 전체 디렉토리 구조 + +``` +frontend/ +├── app/ # Next.js 14 App Router +│ ├── (main)/ # 메인 레이아웃 그룹 +│ ├── (auth)/ # 인증 레이아웃 그룹 +│ ├── (admin)/ # 관리자 레이아웃 그룹 +│ ├── (pop)/ # 팝업 레이아웃 그룹 +│ ├── layout.tsx # 루트 레이아웃 +│ └── registry-provider.tsx # 레지스트리 초기화 프로바이더 +│ +├── components/ # React 컴포넌트 +│ ├── admin/ # 관리자 컴포넌트 (137개) +│ ├── screen/ # 화면 디자이너/뷰어 (139개) +│ ├── dashboard/ # 대시보드 컴포넌트 (32개) +│ ├── dataflow/ # 데이터플로우 관련 (90개) +│ ├── v2/ # V2 컴포넌트 시스템 (28개) +│ ├── common/ # 공통 컴포넌트 (19개) +│ ├── layout/ # 레이아웃 컴포넌트 (10개) +│ ├── ui/ # shadcn/ui 기반 UI (31개) +│ └── ... # 기타 도메인별 컴포넌트 +│ +├── lib/ # 유틸리티 & 라이브러리 +│ ├── api/ # API 클라이언트 (57개 파일) +│ │ ├── client.ts # Axios 기본 설정 +│ │ ├── screen.ts # 화면 관리 API +│ │ ├── user.ts # 사용자 관리 API +│ │ └── ... # 도메인별 API +│ │ +│ ├── registry/ # 컴포넌트 레지스트리 시스템 (540개) +│ │ ├── ComponentRegistry.ts # 컴포넌트 등록 관리 +│ │ ├── WebTypeRegistry.ts # 웹타입 등록 관리 +│ │ ├── LayoutRegistry.ts # 레이아웃 등록 관리 +│ │ ├── init.ts # 레지스트리 초기화 +│ │ ├── DynamicComponentRenderer.tsx # 동적 렌더링 +│ │ ├── components/ # 등록 가능한 컴포넌트들 (288 tsx, 205 ts) +│ │ │ ├── v2-input/ +│ │ │ ├── v2-select/ +│ │ │ ├── text-input/ +│ │ │ ├── entity-search-input/ +│ │ │ ├── modal-repeater-table/ +│ │ │ └── ... +│ │ └── layouts/ # 레이아웃 컴포넌트 +│ │ ├── accordion/ +│ │ ├── grid/ +│ │ ├── flexbox/ +│ │ ├── split/ +│ │ └── tabs/ +│ │ +│ ├── v2-core/ # V2 코어 시스템 +│ │ ├── events/ # 이벤트 버스 +│ │ ├── adapters/ # 레거시 어댑터 +│ │ ├── components/ # V2 공통 컴포넌트 +│ │ ├── services/ # V2 서비스 +│ │ └── init.ts # V2 초기화 +│ │ +│ ├── utils/ # 유틸리티 함수들 (40개+) +│ ├── services/ # 비즈니스 로직 서비스 +│ ├── stores/ # Zustand 스토어 +│ └── constants/ # 상수 정의 +│ +├── contexts/ # React Context (12개) +│ ├── AuthContext.tsx # 인증 컨텍스트 +│ ├── MenuContext.tsx # 메뉴 컨텍스트 +│ ├── ScreenContext.tsx # 화면 컨텍스트 +│ ├── DashboardContext.tsx # 대시보드 컨텍스트 +│ └── ... +│ +├── hooks/ # Custom Hooks (32개) +│ ├── useAuth.ts # 인증 훅 +│ ├── useMenu.ts # 메뉴 관리 훅 +│ ├── useFormValidation.ts # 폼 검증 훅 +│ └── ... +│ +├── types/ # TypeScript 타입 정의 (44개) +│ ├── screen.ts # 화면 관련 타입 +│ ├── component.ts # 컴포넌트 타입 +│ ├── user.ts # 사용자 타입 +│ └── ... +│ +├── providers/ # React Provider +│ └── QueryProvider.tsx # React Query 설정 +│ +├── stores/ # 전역 상태 관리 +│ ├── flowStepStore.ts # 플로우 단계 상태 +│ ├── modalDataStore.ts # 모달 데이터 상태 +│ └── tableDisplayStore.ts # 테이블 표시 상태 +│ +└── public/ # 정적 파일 + ├── favicon.ico + ├── logo.png + └── locales/ # 다국어 파일 +``` + +--- + +## 2. Next.js App Router 구조 + +### 2.1 라우팅 구조 + +WACE ERP는 **Next.js 14 App Router**를 사용하며, 레이아웃 그룹을 활용한 구조화된 라우팅을 제공합니다. + +#### 라우트 그룹 구조 + +``` +app/ +├── layout.tsx # 글로벌 레이아웃 +│ ├── QueryProvider # React Query 초기화 +│ ├── RegistryProvider # 컴포넌트 레지스트리 초기화 +│ ├── Toaster # 토스트 알림 +│ └── ScreenModal # 전역 화면 모달 +│ +├── (main)/ # 메인 애플리케이션 +│ ├── layout.tsx +│ │ ├── AuthProvider # 인증 컨텍스트 +│ │ ├── MenuProvider # 메뉴 컨텍스트 +│ │ └── AppLayout # 사이드바, 헤더 +│ │ +│ ├── main/page.tsx # 메인 페이지 +│ ├── dashboard/ # 대시보드 +│ │ ├── page.tsx # 대시보드 목록 +│ │ └── [dashboardId]/page.tsx # 대시보드 상세 +│ │ +│ ├── screens/[screenId]/page.tsx # 동적 화면 뷰어 +│ │ +│ └── admin/ # 관리자 페이지 +│ ├── page.tsx # 관리자 홈 +│ ├── menu/page.tsx # 메뉴 관리 +│ │ +│ ├── userMng/ # 사용자 관리 +│ │ ├── userMngList/page.tsx +│ │ ├── rolesList/page.tsx +│ │ ├── userAuthList/page.tsx +│ │ └── companyList/page.tsx +│ │ +│ ├── screenMng/ # 화면 관리 +│ │ ├── screenMngList/page.tsx +│ │ ├── dashboardList/page.tsx +│ │ └── reportList/page.tsx +│ │ +│ ├── systemMng/ # 시스템 관리 +│ │ ├── tableMngList/page.tsx +│ │ ├── commonCodeList/page.tsx +│ │ ├── i18nList/page.tsx +│ │ ├── dataflow/page.tsx +│ │ └── collection-managementList/page.tsx +│ │ +│ ├── automaticMng/ # 자동화 관리 +│ │ ├── flowMgmtList/page.tsx +│ │ ├── batchmngList/page.tsx +│ │ ├── exCallConfList/page.tsx +│ │ └── mail/ # 메일 관리 +│ │ ├── accounts/page.tsx +│ │ ├── send/page.tsx +│ │ ├── receive/page.tsx +│ │ └── templates/page.tsx +│ │ +│ └── [...slug]/page.tsx # 동적 관리자 페이지 +│ +├── (auth)/ # 인증 페이지 +│ ├── layout.tsx # 최소 레이아웃 +│ └── login/page.tsx # 로그인 페이지 +│ +├── (admin)/ # 관리자 전용 (별도 레이아웃) +│ └── admin/ +│ ├── vehicle-trips/page.tsx +│ └── vehicle-reports/page.tsx +│ +└── (pop)/ # 팝업 페이지 + ├── layout.tsx # 팝업 레이아웃 + ├── pop/page.tsx + └── work/page.tsx +``` + +### 2.2 페이지 목록 (총 76개) + +**메인 애플리케이션 (50개)** +- `/main` - 메인 페이지 +- `/dashboard` - 대시보드 목록 +- `/dashboard/[dashboardId]` - 대시보드 상세 +- `/screens/[screenId]` - 동적 화면 뷰어 ⭐ 핵심 +- `/multilang` - 다국어 관리 + +**관리자 페이지 (40개+)** +- 사용자 관리: 사용자, 역할, 권한, 회사, 부서 +- 화면 관리: 화면 목록, 대시보드, 리포트 +- 시스템 관리: 테이블, 공통코드, 다국어, 데이터플로우, 컬렉션 +- 자동화 관리: 플로우, 배치, 외부호출, 메일 +- 표준 관리: 웹타입 표준화 +- 캐스케이딩: 관계, 계층, 상호배타, 자동채움 + +**테스트 페이지 (5개)** +- `/test-autocomplete-mapping` +- `/test-entity-search` +- `/test-order-registration` +- `/test-type-safety` +- `/test-flow` + +**인증/기타 (2개)** +- `/login` - 로그인 +- `/pop` - 팝업 페이지 + +### 2.3 Next.js 설정 + +**next.config.mjs** +```javascript +{ + output: "standalone", // Docker 최적화 + eslint: { ignoreDuringBuilds: true }, + typescript: { ignoreBuildErrors: true }, + + // API 프록시: /api/* → http://localhost:8080/api/* + rewrites: [ + { source: "/api/:path*", destination: "http://localhost:8080/api/:path*" } + ], + + // CORS 헤더 + headers: [ + { source: "/api/:path*", headers: [...] } + ], + + // 환경 변수 + env: { + NEXT_PUBLIC_API_URL: "http://localhost:8080/api" + } +} +``` + +--- + +## 3. 컴포넌트 시스템 + +### 3.1 컴포넌트 분류 + +WACE ERP의 컴포넌트는 크게 **4가지 계층**으로 구분됩니다: + +#### 계층 구조 + +``` +1. UI 컴포넌트 (shadcn/ui 기반) - components/ui/ + └─ Button, Input, Select, Dialog, Card 등 기본 UI + +2. 공통 컴포넌트 - components/common/ + └─ ScreenModal, FormValidationIndicator 등 + +3. 도메인 컴포넌트 - components/{domain}/ + ├─ screen/ - 화면 디자이너/뷰어 + ├─ admin/ - 관리자 기능 + ├─ dashboard/ - 대시보드 + └─ dataflow/ - 데이터플로우 + +4. 레지스트리 컴포넌트 - lib/registry/components/ + └─ 동적으로 등록/렌더링되는 컴포넌트들 (90개+) +``` + +### 3.2 주요 컴포넌트 모듈 + +#### 📁 components/screen/ (139개 파일) + +**핵심 컴포넌트** + +| 컴포넌트 | 역할 | 주요 기능 | +|---------|------|----------| +| `ScreenDesigner.tsx` (7095줄) | 화면 디자이너 | - 드래그&드롭으로 컴포넌트 배치
- 그리드 시스템 (12컬럼)
- 실시간 미리보기
- 컴포넌트 설정 패널
- 그룹화/정렬/분산 도구 | +| `InteractiveScreenViewer.tsx` (2472줄) | 화면 뷰어 | - 실제 사용자 화면 렌더링
- 폼 데이터 바인딩
- 버튼 액션 실행
- 검증 처리 | +| `ScreenManagementList.tsx` | 화면 목록 | - 화면 CRUD
- 메뉴 할당
- 다국어 설정 | +| `DynamicWebTypeRenderer.tsx` | 웹타입 렌더러 | - WebTypeRegistry 기반 동적 렌더링 | + +**위젯 컴포넌트 (widgets/types/)** +- TextWidget, NumberWidget, DateWidget +- SelectWidget, CheckboxWidget, RadioWidget +- TextareaWidget, FileWidget, CodeWidget +- ButtonWidget, EntitySearchInputWrapper + +**설정 패널 (config-panels/)** +- 각 웹타입별 설정 패널 (TextConfigPanel, SelectConfigPanel 등) + +**모달 컴포넌트 (modals/)** +- 키보드 단축키, 다국어 설정, 메뉴 할당 등 + +#### 📁 components/admin/ (137개 파일) + +**관리자 기능 컴포넌트** +- 사용자 관리: UserManagementList, RolesManagementList, CompanyList +- 화면 관리: ScreenManagementList, DashboardManagementList +- 테이블 관리: TableManagementList, ColumnEditor +- 공통코드 관리: CommonCodeManagement +- 메뉴 관리: MenuManagement + +#### 📁 components/dashboard/ (32개 파일) + +**대시보드 컴포넌트** +- DashboardViewer: 대시보드 렌더링 +- DashboardWidgets: 차트, 테이블, 카드 등 위젯 +- ChartComponents: 라인, 바, 파이, 도넛 차트 + +#### 📁 components/dataflow/ (90개+ 파일) + +**데이터플로우 시스템** +- DataFlowList: 플로우 목록 +- node-editor/: 노드 편집기 +- connection/: 커넥션 관리 (38개 파일) +- condition/: 조건 설정 +- external-call/: 외부 호출 설정 + +--- + +## 4. V2 컴포넌트 시스템 + +V2는 **통합 컴포넌트 시스템**으로, 유사한 기능을 하나의 컴포넌트로 통합하여 개발 효율성을 높입니다. + +### 4.1 V2 컴포넌트 종류 (9개) + +| ID | 이름 | 설명 | 포함 기능 | +|----|------|------|----------| +| `v2-input` | 통합 입력 | 텍스트, 숫자, 비밀번호, 슬라이더, 컬러 등 | text, number, password, email, tel, textarea, slider, color | +| `v2-select` | 통합 선택 | 드롭다운, 라디오, 체크박스, 태그, 토글 등 | dropdown, radio, checkbox, tagbox, toggle, swap, combobox | +| `v2-date` | 통합 날짜 | 날짜, 시간, 날짜시간, 날짜 범위 | date, time, datetime, daterange | +| `v2-list` | 통합 목록 | 테이블, 카드, 칸반, 리스트 | table, card, kanban, list, grid | +| `v2-layout` | 통합 레이아웃 | 그리드, 분할 패널, 플렉스 | grid, split, flex, masonry | +| `v2-group` | 통합 그룹 | 탭, 아코디언, 섹션, 모달 | tabs, accordion, section, modal, drawer | +| `v2-media` | 통합 미디어 | 이미지, 비디오, 오디오, 파일 업로드 | image, video, audio, file | +| `v2-biz` | 통합 비즈니스 | 플로우, 랙, 채번규칙, 카테고리 | flow, rack, numbering, category | +| `v2-repeater` | 통합 반복 | 인라인 테이블, 모달, 버튼 | inline-table, modal, button, selected-items | + +### 4.2 V2 아키텍처 + +``` +components/v2/ # V2 UI 컴포넌트 +├── V2Input.tsx # 통합 입력 컴포넌트 +├── V2Select.tsx # 통합 선택 컴포넌트 +├── V2Date.tsx # 통합 날짜 컴포넌트 +├── V2List.tsx # 통합 목록 컴포넌트 +├── V2Layout.tsx # 통합 레이아웃 컴포넌트 +├── V2Group.tsx # 통합 그룹 컴포넌트 +├── V2Media.tsx # 통합 미디어 컴포넌트 +├── V2Biz.tsx # 통합 비즈니스 컴포넌트 +├── V2Hierarchy.tsx # 통합 계층 컴포넌트 +├── V2Repeater.tsx # 통합 반복 컴포넌트 +├── V2FormContext.tsx # V2 폼 컨텍스트 +├── V2ComponentRenderer.tsx # V2 렌더러 +├── registerV2Components.ts # V2 등록 함수 +└── config-panels/ # V2 설정 패널 + ├── V2InputConfigPanel.tsx + ├── V2SelectConfigPanel.tsx + └── ... + +lib/v2-core/ # V2 코어 라이브러리 +├── events/ # 이벤트 버스 +│ ├── EventBus.ts # 발행/구독 패턴 +│ └── types.ts # 이벤트 타입 +├── adapters/ # 레거시 어댑터 +│ └── LegacyEventAdapter.ts # 레거시 시스템 연동 +├── components/ # V2 공통 컴포넌트 +│ └── V2ErrorBoundary.tsx # 에러 경계 +├── services/ # V2 서비스 +│ ├── ScheduleGeneratorService.ts +│ └── ScheduleConfirmDialog.tsx +└── init.ts # V2 초기화 + +lib/registry/components/v2-*/ # V2 렌더러 (레지스트리용) +├── v2-input/ +│ ├── V2InputRenderer.tsx # 자동 등록 렌더러 +│ └── index.ts # 컴포넌트 정의 +├── v2-select/ +│ ├── V2SelectRenderer.tsx +│ └── index.ts +└── ... +``` + +### 4.3 V2 초기화 흐름 + +```typescript +// 1. app/registry-provider.tsx +useEffect(() => { + // 레거시 레지스트리 초기화 + initializeRegistries(); + + // V2 Core 초기화 + initV2Core({ + debug: false, + legacyBridge: { legacyToV2: true, v2ToLegacy: true } + }); +}, []); + +// 2. lib/registry/init.ts +export function initializeRegistries() { + // 웹타입 등록 (text, number, date 등) + initializeWebTypeRegistry(); + + // V2 컴포넌트 등록 + registerV2Components(); +} + +// 3. components/v2/registerV2Components.ts +export function registerV2Components() { + for (const definition of v2ComponentDefinitions) { + ComponentRegistry.registerComponent(definition); + } +} +``` + +### 4.4 V2 렌더링 흐름 + +``` +사용자 요청 (screens/[screenId]) + ↓ +InteractiveScreenViewer + ↓ +DynamicComponentRenderer + ↓ +┌─ componentType이 v2-* 인가? ─┐ +│ Yes │ No +↓ ↓ +ComponentRegistry.getComponent LegacyComponentRegistry.get + ↓ ↓ +V2*Renderer (클래스 기반) 레거시 렌더러 (함수) + ↓ ↓ +render() → V2* 컴포넌트 직접 렌더링 + ↓ +V2FormContext 연동 (선택) + ↓ +최종 렌더링 +``` + +### 4.5 V2 vs 레거시 비교 + +| 항목 | 레거시 시스템 | V2 시스템 | +|------|--------------|----------| +| 컴포넌트 수 | 90개+ (분산) | 9개 (통합) | +| 렌더러 패턴 | 함수형 렌더러 | 클래스 기반 자동 등록 | +| 폼 통합 | 개별 구현 | V2FormContext 통합 | +| 이벤트 시스템 | props drilling | V2 EventBus (발행/구독) | +| 에러 처리 | 개별 처리 | V2ErrorBoundary 통합 | +| 설정 패널 | 개별 패널 | 통합 설정 시스템 | +| 핫 리로드 | 미지원 | 개발 모드 지원 | + +--- + +## 5. 화면 디자이너 워크플로우 + +### 5.1 화면 디자이너 구성 + +**ScreenDesigner.tsx** (7095줄) - 핵심 컴포넌트 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Screen Designer │ +├─────────────────────────────────────────────────────────────────┤ +│ 탭1: 디자인 | 탭2: 프리뷰 | 탭3: 다국어 | 탭4: 메뉴할당 | 탭5: 스타일 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ [컴포넌트 팔레트] [캔버스] [속성 패널] │ +│ ┌──────────────┐ ┌────────────────┐ ┌──────────────┐ │ +│ │ 입력 컴포넌트 │ │ │ │ 기본 설정 │ │ +│ │ - Text │ │ 드래그&드롭 │ │ - ID │ │ +│ │ - Number │ │ 컴포넌트 │ │ - 라벨 │ │ +│ │ - Date │ ───▶ │ 배치 영역 │ ◀── │ - 위치/크기 │ │ +│ │ - Select │ │ │ │ - 데이터연결 │ │ +│ │ │ │ 그리드 기반 │ │ │ │ +│ │ 표시 컴포넌트 │ │ 12컬럼 │ │ 고급 설정 │ │ +│ │ - Table │ │ │ │ - 조건부표시 │ │ +│ │ - Card │ │ │ │ - 검증규칙 │ │ +│ │ │ │ │ │ - 버튼액션 │ │ +│ │ 레이아웃 │ └────────────────┘ └──────────────┘ │ +│ │ - Grid │ │ +│ │ - Tabs │ [하단 도구] │ +│ │ - Accordion │ ┌─────────────────────────────────────┐ │ +│ │ │ │ 그룹화 | 정렬 | 분산 | 라벨토글 │ │ +│ └──────────────┘ └─────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 5.2 주요 기능 + +#### 그리드 시스템 +```typescript +// 12컬럼 그리드 기반 +const GRID_COLUMNS = 12; +const COLUMN_WIDTH = (containerWidth - gaps) / 12; + +// 컴포넌트 너비 → 컬럼 스팬 변환 +function calculateColumnSpan(width: number): number { + return Math.max(1, Math.min(12, Math.round(width / COLUMN_WIDTH))); +} + +// 반응형 지원 +{ + default: { span: 6 }, // 기본 (50%) + sm: { span: 12 }, // 모바일 (100%) + md: { span: 6 }, // 태블릿 (50%) + lg: { span: 4 } // 데스크톱 (33%) +} +``` + +#### 컴포넌트 배치 흐름 + +``` +1. 컴포넌트 팔레트에서 드래그 시작 + ↓ +2. 캔버스에 드롭 + ↓ +3. 컴포넌트 생성 (generateComponentId) + ↓ +4. 위치 계산 (10px 단위 스냅) + ↓ +5. 그리드 정렬 (12컬럼 기준) + ↓ +6. 레이아웃 데이터 업데이트 + ↓ +7. 리렌더링 (RealtimePreview) +``` + +#### 컴포넌트 설정 + +``` +선택된 컴포넌트 → 우측 속성 패널 표시 + ↓ +┌─────────────────────────────────────┐ +│ 기본 설정 │ +│ - ID: comp_1234 │ +│ - 라벨: "제품명" │ +│ - 컬럼명: product_name │ +│ - 필수: ☑ │ +│ - 읽기전용: ☐ │ +│ │ +│ 크기 & 위치 │ +│ - X: 100px, Y: 200px │ +│ - 너비: 300px, 높이: 40px │ +│ - 컬럼 스팬: 6 │ +│ │ +│ 데이터 연결 │ +│ - 테이블: product_info │ +│ - 컬럼: product_name │ +│ - 웹타입: text │ +│ │ +│ 고급 설정 │ +│ - 조건부 표시: field === 'A' │ +│ - 버튼 액션: [등록], [수정], [삭제] │ +│ - 데이터플로우: flow_123 │ +└─────────────────────────────────────┘ +``` + +### 5.3 저장 구조 + +**ScreenDefinition (JSON)** + +```json +{ + "screenId": 123, + "screenCode": "PRODUCT_MGMT", + "screenName": "제품 관리", + "tableName": "product_info", + "screenType": "form", + "version": 2, + "layoutData": { + "components": [ + { + "id": "comp_text_1", + "type": "text-input", + "componentType": "text-input", + "label": "제품명", + "columnName": "product_name", + "position": { "x": 100, "y": 50, "z": 0 }, + "size": { "width": 300, "height": 40 }, + "componentConfig": { + "required": true, + "placeholder": "제품명을 입력하세요", + "maxLength": 100 + }, + "style": { + "labelDisplay": true, + "labelText": "제품명" + } + }, + { + "id": "comp_table_1", + "type": "table-list", + "componentType": "table-list", + "tableName": "product_info", + "position": { "x": 50, "y": 200, "z": 0 }, + "size": { "width": 800, "height": 400 }, + "componentConfig": { + "columns": ["product_code", "product_name", "price"], + "pagination": true, + "sortable": true + } + } + ], + "layouts": [ + { + "id": "layout_tabs_1", + "layoutType": "tabs", + "children": [ + { "tabId": "tab1", "title": "기본정보", "components": ["comp_text_1"] }, + { "tabId": "tab2", "title": "상세정보", "components": ["comp_table_1"] } + ] + } + ] + }, + "createdBy": "user123", + "createdDate": "2024-01-01T00:00:00Z" +} +``` + +--- + +## 6. API 클라이언트 시스템 + +### 6.1 API 클라이언트 구조 + +**lib/api/** (57개 파일) + +``` +lib/api/ +├── client.ts # Axios 기본 설정 & 인터셉터 +│ +├── 화면 관련 (3개) +│ ├── screen.ts # 화면 CRUD +│ ├── screenGroup.ts # 화면 그룹 +│ └── screenFile.ts # 화면 파일 +│ +├── 사용자 관련 (5개) +│ ├── user.ts # 사용자 관리 +│ ├── role.ts # 역할 관리 +│ ├── company.ts # 회사 관리 +│ └── department.ts # 부서 관리 +│ +├── 테이블 관련 (5개) +│ ├── tableManagement.ts # 테이블 관리 +│ ├── tableSchema.ts # 스키마 조회 +│ ├── tableHistory.ts # 테이블 이력 +│ └── tableCategoryValue.ts # 카테고리 값 +│ +├── 데이터플로우 (6개) +│ ├── dataflow.ts # 데이터플로우 정의 +│ ├── dataflowSave.ts # 저장 로직 +│ ├── nodeFlows.ts # 노드 플로우 +│ ├── nodeExternalConnections.ts # 외부 연결 +│ └── flowExternalDb.ts # 외부 DB 플로우 +│ +├── 자동화 (4개) +│ ├── batch.ts # 배치 작업 +│ ├── batchManagement.ts # 배치 관리 +│ ├── externalCall.ts # 외부 호출 +│ └── externalCallConfig.ts # 외부 호출 설정 +│ +├── 시스템 (8개) +│ ├── menu.ts # 메뉴 관리 +│ ├── commonCode.ts # 공통코드 +│ ├── multilang.ts # 다국어 +│ ├── layout.ts # 레이아웃 +│ ├── collection.ts # 컬렉션 +│ └── ... +│ +└── 기타 (26개) + ├── data.ts # 동적 데이터 CRUD + ├── dynamicForm.ts # 동적 폼 + ├── file.ts # 파일 업로드 + ├── dashboard.ts # 대시보드 + ├── mail.ts # 메일 + ├── reportApi.ts # 리포트 + └── ... +``` + +### 6.2 API 클라이언트 기본 설정 + +**lib/api/client.ts** + +```typescript +// 1. 동적 API URL 설정 +const getApiBaseUrl = (): string => { + // 환경변수 우선 + if (process.env.NEXT_PUBLIC_API_URL) return process.env.NEXT_PUBLIC_API_URL; + + // 프로덕션: v1.vexplor.com → api.vexplor.com + if (currentHost === "v1.vexplor.com") { + return "https://api.vexplor.com/api"; + } + + // 로컬: localhost:9771 → localhost:8080 + return "http://localhost:8080/api"; +}; + +export const API_BASE_URL = getApiBaseUrl(); + +// 2. Axios 인스턴스 생성 +export const apiClient = axios.create({ + baseURL: API_BASE_URL, + timeout: 30000, + headers: { "Content-Type": "application/json" }, + withCredentials: true +}); + +// 3. 요청 인터셉터 (JWT 토큰 자동 추가) +apiClient.interceptors.request.use((config) => { + const token = localStorage.getItem("authToken"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + + // 다국어: GET 요청에 userLang 파라미터 추가 + if (config.method === "GET") { + config.params = { + ...config.params, + userLang: window.__GLOBAL_USER_LANG || "KR" + }; + } + + return config; +}); + +// 4. 응답 인터셉터 (토큰 갱신, 401 처리) +apiClient.interceptors.response.use( + (response) => { + // 서버에서 새 토큰 전송 시 자동 갱신 + const newToken = response.headers["x-new-token"]; + if (newToken) { + localStorage.setItem("authToken", newToken); + } + return response; + }, + async (error) => { + // 401 에러: 토큰 갱신 시도 → 실패 시 로그인 페이지 + if (error.response?.status === 401) { + const newToken = await refreshToken(); + if (newToken) { + error.config.headers.Authorization = `Bearer ${newToken}`; + return apiClient.request(error.config); // 재시도 + } + window.location.href = "/login"; + } + return Promise.reject(error); + } +); +``` + +### 6.3 API 사용 패턴 + +#### ✅ 올바른 사용법 (lib/api 클라이언트 사용) + +```typescript +import { screenApi } from "@/lib/api/screen"; +import { dataApi } from "@/lib/api/data"; + +// 화면 목록 조회 +const screens = await screenApi.getScreens({ page: 1, size: 20 }); + +// 데이터 생성 +const result = await dataApi.createData("product_info", { + product_name: "제품A", + price: 10000 +}); +``` + +#### ❌ 잘못된 사용법 (fetch 직접 사용 금지!) + +```typescript +// 🚫 금지! JWT 토큰, 다국어, 에러 처리 누락 +const res = await fetch('/api/screen-management/screens'); +``` + +### 6.4 주요 API 예시 + +#### 화면 관리 API (screen.ts) + +```typescript +export const screenApi = { + // 화면 목록 조회 + getScreens: async (params) => { + const response = await apiClient.get("/screen-management/screens", { params }); + return response.data; + }, + + // 화면 상세 조회 + getScreen: async (screenId: number) => { + const response = await apiClient.get(`/screen-management/screens/${screenId}`); + return response.data.data; + }, + + // 화면 생성 + createScreen: async (screen: CreateScreenRequest) => { + const response = await apiClient.post("/screen-management/screens", screen); + return response.data; + }, + + // 화면 수정 + updateScreen: async (screenId: number, screen: UpdateScreenRequest) => { + const response = await apiClient.put(`/screen-management/screens/${screenId}`, screen); + return response.data; + }, + + // 화면 삭제 + deleteScreen: async (screenId: number) => { + const response = await apiClient.delete(`/screen-management/screens/${screenId}`); + return response.data; + } +}; +``` + +#### 동적 데이터 API (data.ts) + +```typescript +export const dataApi = { + // 데이터 목록 조회 (페이징, 필터링, 정렬) + getDataList: async (tableName: string, params: { + page?: number; + size?: number; + filters?: Record; + sortBy?: string; + sortOrder?: "asc" | "desc"; + }) => { + const response = await apiClient.get(`/data/${tableName}`, { params }); + return response.data; + }, + + // 데이터 생성 + createData: async (tableName: string, data: Record) => { + const response = await apiClient.post(`/data/${tableName}`, data); + return response.data; + }, + + // 데이터 수정 + updateData: async (tableName: string, id: number, data: Record) => { + const response = await apiClient.put(`/data/${tableName}/${id}`, data); + return response.data; + }, + + // 데이터 삭제 + deleteData: async (tableName: string, id: number) => { + const response = await apiClient.delete(`/data/${tableName}/${id}`); + return response.data; + } +}; +``` + +--- + +## 7. 상태 관리 + +### 7.1 상태 관리 전략 + +WACE ERP는 **하이브리드 상태 관리 전략**을 사용합니다: + +| 관리 방식 | 사용 시나리오 | 예시 | +|----------|-------------|------| +| **React Query** | 서버 상태 (캐싱, 자동 갱신) | 화면 목록, 데이터 목록 | +| **React Context** | 전역 상태 (공유 데이터) | 인증, 메뉴, 화면 | +| **Zustand** | 클라이언트 상태 (간단한 전역) | 플로우 단계, 모달 데이터 | +| **Local State** | 컴포넌트 로컬 상태 | 폼 입력, UI 토글 | + +### 7.2 React Context (12개) + +``` +contexts/ +├── AuthContext.tsx # 인증 상태 & 세션 관리 +│ - 사용자 정보 +│ - 로그인/로그아웃 +│ - 세션 타이머 (30분) +│ +├── MenuContext.tsx # 메뉴 트리 & 네비게이션 +│ - 메뉴 구조 +│ - 현재 선택된 메뉴 +│ - 메뉴 접근 권한 +│ +├── ScreenContext.tsx # 화면 편집 상태 +│ - 현재 편집 중인 화면 +│ - 선택된 컴포넌트 +│ - 실행 취소/다시 실행 +│ +├── ScreenPreviewContext.tsx # 화면 미리보기 +│ - 반응형 모드 (데스크톱/태블릿/모바일) +│ - 미리보기 데이터 +│ +├── DashboardContext.tsx # 대시보드 상태 +│ - 대시보드 레이아웃 +│ - 위젯 설정 +│ +├── TableOptionsContext.tsx # 테이블 옵션 +│ - 컬럼 순서 +│ - 필터 +│ - 정렬 +│ +├── ActiveTabContext.tsx # 탭 활성화 상태 +├── LayerContext.tsx # 레이어 관리 +├── ReportDesignerContext.tsx # 리포트 디자이너 +├── ScreenMultiLangContext.tsx # 화면 다국어 +├── SplitPanelContext.tsx # 분할 패널 상태 +└── TableSearchWidgetHeightContext.tsx # 테이블 검색 높이 +``` + +### 7.3 React Query 설정 + +**providers/QueryProvider.tsx** + +```typescript +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5분간 fresh + cacheTime: 10 * 60 * 1000, // 10분간 캐시 유지 + refetchOnWindowFocus: false, // 창 포커스 시 자동 갱신 비활성화 + retry: 1, // 실패 시 1회 재시도 + }, + }, +}); + +export function QueryProvider({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +**사용 예시** + +```typescript +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { screenApi } from "@/lib/api/screen"; + +// 화면 목록 조회 (자동 캐싱) +const { data: screens, isLoading, error } = useQuery({ + queryKey: ["screens", { page: 1 }], + queryFn: () => screenApi.getScreens({ page: 1, size: 20 }) +}); + +// 화면 생성 (생성 후 목록 자동 갱신) +const queryClient = useQueryClient(); +const createMutation = useMutation({ + mutationFn: (screen: CreateScreenRequest) => screenApi.createScreen(screen), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["screens"] }); + } +}); +``` + +### 7.4 Zustand 스토어 (3개) + +``` +stores/ +├── flowStepStore.ts # 플로우 단계 상태 +│ - 현재 단계 +│ - 단계별 데이터 +│ - 진행률 +│ +├── modalDataStore.ts # 모달 데이터 공유 +│ - 모달 ID별 데이터 +│ - 모달 간 데이터 전달 +│ +└── tableDisplayStore.ts # 테이블 표시 상태 + - 선택된 행 + - 정렬 상태 + - 페이지네이션 +``` + +**사용 예시** + +```typescript +import { create } from "zustand"; + +interface ModalDataStore { + modalData: Record; + setModalData: (modalId: string, data: any) => void; + getModalData: (modalId: string) => any; +} + +export const useModalDataStore = create((set, get) => ({ + modalData: {}, + + setModalData: (modalId, data) => { + set((state) => ({ + modalData: { ...state.modalData, [modalId]: data } + })); + }, + + getModalData: (modalId) => { + return get().modalData[modalId]; + } +})); +``` + +--- + +## 8. 레지스트리 시스템 + +레지스트리 시스템은 **컴포넌트를 동적으로 등록하고 렌더링**하는 핵심 아키텍처입니다. + +### 8.1 레지스트리 종류 + +``` +lib/registry/ +├── ComponentRegistry.ts # 컴포넌트 레지스트리 (신규) +│ - 90개+ 컴포넌트 관리 +│ - 자동 등록 시스템 +│ - 핫 리로드 지원 +│ +├── WebTypeRegistry.ts # 웹타입 레지스트리 (레거시) +│ - text, number, date 등 기본 웹타입 +│ - 위젯 컴포넌트 + 설정 패널 +│ +└── LayoutRegistry.ts # 레이아웃 레지스트리 + - grid, tabs, accordion 등 레이아웃 +``` + +### 8.2 ComponentRegistry 상세 + +**등록 프로세스** + +```typescript +// 1. 컴포넌트 정의 (lib/registry/components/v2-input/index.ts) +export const V2InputDefinition: ComponentDefinition = { + id: "v2-input", + name: "통합 입력", + category: ComponentCategory.V2, + component: V2InputRenderer, + configPanel: V2InputConfigPanel, + defaultSize: { width: 200, height: 40 }, + defaultConfig: { inputType: "text", format: "none" } +}; + +// 2. 자동 등록 렌더러 (lib/registry/components/v2-input/V2InputRenderer.tsx) +export class V2InputRenderer extends AutoRegisteringComponentRenderer { + static componentDefinition = V2InputDefinition; + + render(): React.ReactElement { + return ; + } +} + +// 자동 등록 실행 +V2InputRenderer.registerSelf(); + +// 3. 레지스트리 조회 & 렌더링 (DynamicComponentRenderer.tsx) +const newComponent = ComponentRegistry.getComponent("v2-input"); +if (newComponent) { + const Renderer = newComponent.component; + return ; +} +``` + +### 8.3 등록된 컴포넌트 목록 (90개+) + +**입력 컴포넌트 (25개)** +- text-input, number-input, date-input +- select-basic, checkbox-basic, radio-basic, toggle-switch +- textarea-basic, slider-basic +- v2-input, v2-select, v2-date + +**표시 컴포넌트 (15개)** +- text-display, image-display, card-display +- table-list, v2-table-list, pivot-grid +- aggregation-widget, v2-aggregation-widget + +**레이아웃 컴포넌트 (10개)** +- grid-layout, flexbox-layout +- tabs-widget, accordion-basic +- split-panel-layout, v2-split-panel-layout +- section-card, section-paper +- v2-divider-line + +**비즈니스 컴포넌트 (20개)** +- entity-search-input, autocomplete-search-input +- modal-repeater-table, repeat-screen-modal +- selected-items-detail-input, simple-repeater-table +- repeater-field-group, repeat-container +- category-manager, v2-category-manager +- numbering-rule, v2-numbering-rule +- rack-structure, v2-rack-structure +- location-swap-selector, customer-item-mapping + +**특수 컴포넌트 (20개)** +- button-primary, v2-button-primary +- file-upload, v2-file-upload +- mail-recipient-selector +- conditional-container, universal-form-modal +- related-data-buttons +- flow-widget +- map + +### 8.4 DynamicComponentRenderer 동작 원리 + +```typescript +// DynamicComponentRenderer.tsx (770줄) + +export const DynamicComponentRenderer: React.FC = ({ component, ...props }) => { + // 1. 컴포넌트 타입 추출 + const componentType = component.componentType || component.type; + + // 2. 레거시 → V2 자동 매핑 + const v2Type = componentType.startsWith("v2-") + ? componentType + : ComponentRegistry.hasComponent(`v2-${componentType}`) + ? `v2-${componentType}` + : componentType; + + // 3. 조건부 렌더링 체크 + if (component.conditionalConfig?.enabled) { + const conditionMet = evaluateCondition(props.formData); + if (!conditionMet) return null; + } + + // 4. 카테고리 타입 우선 처리 + if (component.inputType === "category" || component.webType === "category") { + return ; + } + + // 5. 레이아웃 컴포넌트 + if (componentType === "layout") { + return ; + } + + // 6. ComponentRegistry에서 조회 (신규) + const newComponent = ComponentRegistry.getComponent(v2Type); + if (newComponent) { + const Renderer = newComponent.component; + // 클래스 기반: new Renderer(props).render() + // 함수형: + return isClass(Renderer) + ? new Renderer(props).render() + : ; + } + + // 7. LegacyComponentRegistry에서 조회 (레거시) + const legacyRenderer = legacyComponentRegistry.get(componentType); + if (legacyRenderer) { + return legacyRenderer({ component, ...props }); + } + + // 8. 폴백: 미등록 컴포넌트 플레이스홀더 + return ; +}; +``` + +--- + +## 9. 대시보드 시스템 + +### 9.1 대시보드 구조 + +``` +components/dashboard/ +├── DashboardViewer.tsx # 대시보드 렌더링 +├── DashboardGrid.tsx # 그리드 레이아웃 +├── DashboardWidget.tsx # 위젯 래퍼 +│ +├── widgets/ # 위젯 컴포넌트 +│ ├── ChartWidget.tsx # 차트 위젯 +│ ├── TableWidget.tsx # 테이블 위젯 +│ ├── CardWidget.tsx # 카드 위젯 +│ ├── StatWidget.tsx # 통계 위젯 +│ └── CustomWidget.tsx # 커스텀 위젯 +│ +└── charts/ # 차트 컴포넌트 + ├── LineChart.tsx + ├── BarChart.tsx + ├── PieChart.tsx + ├── DonutChart.tsx + └── AreaChart.tsx +``` + +### 9.2 대시보드 JSON 구조 + +```json +{ + "dashboardId": 1, + "dashboardName": "영업 대시보드", + "layout": { + "type": "grid", + "columns": 12, + "rows": 6, + "gap": 16 + }, + "widgets": [ + { + "widgetId": "widget_1", + "widgetType": "chart", + "chartType": "line", + "title": "월별 매출 추이", + "position": { "x": 0, "y": 0, "w": 6, "h": 3 }, + "dataSource": { + "type": "api", + "endpoint": "/api/data/sales_monthly", + "filters": { "year": 2024 } + }, + "chartConfig": { + "xAxis": "month", + "yAxis": "sales_amount", + "showLegend": true + } + }, + { + "widgetId": "widget_2", + "widgetType": "stat", + "title": "총 매출", + "position": { "x": 6, "y": 0, "w": 3, "h": 2 }, + "dataSource": { + "type": "sql", + "query": "SELECT SUM(amount) FROM sales WHERE year = 2024" + }, + "statConfig": { + "format": "currency", + "comparison": "lastYear" + } + } + ] +} +``` + +### 9.3 대시보드 라우팅 + +``` +/dashboard # 대시보드 목록 +/dashboard/[dashboardId] # 대시보드 보기 +/admin/screenMng/dashboardList # 대시보드 관리 (편집) +``` + +--- + +## 10. 다국어 지원 + +### 10.1 다국어 시스템 구조 + +WACE ERP는 **동적 다국어 시스템**을 제공합니다 (DB 기반): + +``` +다국어 흐름 + ↓ +1. 사용자 로그인 + ↓ +2. 사용자 로케일 조회 (GET /api/admin/user-locale) + → 결과: "KR" | "EN" | "CN" + ↓ +3. 전역 상태에 저장 + - window.__GLOBAL_USER_LANG + - localStorage.userLocale + ↓ +4. API 요청 시 자동 주입 + - GET 요청: ?userLang=KR (apiClient 인터셉터) + ↓ +5. 백엔드에서 다국어 데이터 반환 + - label_KR, label_EN, label_CN + ↓ +6. 프론트엔드에서 표시 + - extractMultilangLabel(label, "KR") +``` + +### 10.2 다국어 API + +**lib/api/multilang.ts** + +```typescript +export const multilangApi = { + // 다국어 데이터 조회 + getMultilangData: async (params: { + target_table: string; + target_pk: string; + target_lang: string; + }) => { + const response = await apiClient.get("/admin/multilang", { params }); + return response.data; + }, + + // 다국어 데이터 저장 + saveMultilangData: async (data: { + target_table: string; + target_pk: string; + target_field: string; + target_lang: string; + translated_text: string; + }) => { + const response = await apiClient.post("/admin/multilang", data); + return response.data; + } +}; +``` + +### 10.3 다국어 유틸리티 + +**lib/utils/multilang.ts** + +```typescript +// 다국어 라벨 추출 +export function extractMultilangLabel( + label: string | Record | undefined, + locale: string = "KR" +): string { + if (!label) return ""; + + // 문자열인 경우 그대로 반환 + if (typeof label === "string") return label; + + // 객체인 경우 로케일에 맞는 값 반환 + return label[locale] || label["KR"] || label["EN"] || ""; +} + +// 화면 컴포넌트 라벨 추출 +export function getComponentLabel(component: ComponentData, locale: string): string { + // 우선순위: multiLangLabel > label > columnName > id + if (component.multiLangLabel) { + return extractMultilangLabel(component.multiLangLabel, locale); + } + return component.label || component.columnName || component.id; +} +``` + +### 10.4 화면 디자이너 다국어 탭 + +``` +ScreenDesigner + ↓ +┌───────────────────────────────────────────────────┐ +│ 탭: 다국어 설정 │ +├───────────────────────────────────────────────────┤ +│ │ +│ 화면명 다국어 │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ 한국어(KR): 제품 관리 │ │ +│ │ 영어(EN): Product Management │ │ +│ │ 중국어(CN): 产品管理 │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ +│ 컴포넌트별 다국어 │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ [comp_text_1] 제품명 │ │ +│ │ 한국어(KR): 제품명 │ │ +│ │ 영어(EN): Product Name │ │ +│ │ 중국어(CN): 产品名称 │ │ +│ │ │ │ +│ │ [comp_text_2] 가격 │ │ +│ │ 한국어(KR): 가격 │ │ +│ │ 영어(EN): Price │ │ +│ │ 중국어(CN): 价格 │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ +│ [자동 번역] [일괄 적용] [저장] │ +└───────────────────────────────────────────────────┘ +``` + +--- + +## 11. 인증 플로우 + +### 11.1 인증 아키텍처 + +``` +┌─────────────────────────────────────────────────────┐ +│ Frontend │ +├─────────────────────────────────────────────────────┤ +│ │ +│ useAuth Hook │ +│ ├─ login(userId, password) │ +│ ├─ logout() │ +│ ├─ refreshUserData() │ +│ ├─ checkAuthStatus() │ +│ └─ switchCompany(companyCode) ⭐ 신규 │ +│ │ +│ AuthContext Provider │ +│ ├─ SessionManager (30분 타임아웃) │ +│ ├─ Session Warning (5분 전 알림) │ +│ └─ Auto Refresh (활동 감지) │ +│ │ +│ TokenManager │ +│ ├─ getToken(): localStorage.authToken │ +│ ├─ setToken(token): 저장 + 쿠키 설정 │ +│ ├─ removeToken(): 삭제 + 쿠키 삭제 │ +│ └─ isTokenExpired(token): JWT 검증 │ +│ │ +│ API Client Interceptor │ +│ ├─ Request: JWT 토큰 자동 추가 │ +│ └─ Response: 401 시 토큰 갱신 또는 로그아웃 │ +│ │ +└─────────────────────────────────────────────────────┘ + │ + │ HTTP Request + │ Authorization: Bearer + ↓ +┌─────────────────────────────────────────────────────┐ +│ Backend (8080) │ +├─────────────────────────────────────────────────────┤ +│ │ +│ POST /api/auth/login │ +│ → JWT 토큰 발급 (24시간) │ +│ │ +│ POST /api/auth/refresh │ +│ → JWT 토큰 갱신 │ +│ │ +│ POST /api/auth/logout │ +│ → 세션 무효화 │ +│ │ +│ GET /api/auth/me │ +│ → 현재 사용자 정보 │ +│ │ +│ GET /api/auth/status │ +│ → 인증 상태 확인 │ +│ │ +│ POST /api/auth/switch-company ⭐ 신규 │ +│ → 회사 전환 (WACE 관리자 전용) │ +│ │ +└─────────────────────────────────────────────────────┘ +``` + +### 11.2 로그인 흐름 + +``` +1. 사용자가 ID/PW 입력 → /login 페이지 + ↓ +2. POST /api/auth/login { userId, password } + ↓ +3. 백엔드 검증 (DB: user_info 테이블) + ├─ 성공: JWT 토큰 발급 (payload: userId, companyCode, isAdmin, exp) + └─ 실패: 401 에러 + ↓ +4. 프론트엔드: 토큰 저장 + - localStorage.setItem("authToken", token) + - document.cookie = "authToken=..." + ↓ +5. 사용자 정보 조회 + - GET /api/auth/me + - GET /api/admin/user-locale + ↓ +6. 전역 상태 업데이트 + - AuthContext.user + - window.__GLOBAL_USER_LANG + ↓ +7. 메인 페이지로 리다이렉트 (/main) +``` + +### 11.3 세션 관리 + +**SessionManager (lib/sessionManager.ts)** + +```typescript +// 설정 +{ + checkInterval: 60000, // 1분마다 체크 + maxInactiveTime: 1800000, // 30분 (데스크톱) + warningTime: 300000, // 5분 전 경고 +} + +// 이벤트 +{ + onWarning: (remainingTime) => { + // "세션이 5분 후 만료됩니다" 알림 표시 + }, + onExpiry: () => { + // 자동 로그아웃 → 로그인 페이지 + }, + onActivity: () => { + // 사용자 활동 감지 → 타이머 리셋 + } +} +``` + +### 11.4 토큰 갱신 전략 + +``` +자동 토큰 갱신 트리거: +1. 10분마다 토큰 상태 확인 (타이머) +2. 사용자 활동 감지 (클릭, 키보드, 스크롤) +3. API 401 응답 (토큰 만료) + +갱신 로직: + ↓ +1. 현재 토큰 만료까지 30분 미만? + ↓ Yes +2. POST /api/auth/refresh + ├─ 성공: 새 토큰 저장 + └─ 실패: 로그아웃 +``` + +### 11.5 회사 전환 (WACE 관리자 전용) ⭐ + +``` +1. WACE 관리자 로그인 + ↓ +2. 헤더에서 회사 선택 (CompanySwitcher) + ↓ +3. POST /api/auth/switch-company { companyCode: "AAA" } + ↓ +4. 백엔드: 새 JWT 발급 (payload.companyCode = "AAA") + ↓ +5. 프론트엔드: 새 토큰 저장 + ↓ +6. 페이지 새로고침 → 화면/데이터가 AAA 회사 기준으로 표시 +``` + +--- + +## 12. 사용자 워크플로우 + +### 12.1 전체 워크플로우 개요 + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 관리자 (화면 생성) │ +└──────────────────────────────────────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 1. 로그인 (WACE 관리자) │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 2. 화면 디자이너 접속 │ + │ /admin/screenMng/ │ + │ screenMngList │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 3. 화면 생성 & 편집 │ + │ - 컴포넌트 배치 │ + │ - 데이터 연결 │ + │ - 버튼 액션 설정 │ + │ - 다국어 설정 │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 4. 메뉴에 화면 할당 │ + │ /admin/menu │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 5. 사용자에게 권한 부여 │ + │ /admin/userMng/ │ + │ userAuthList │ + └─────────────────────────────┘ + │ + ↓ +┌──────────────────────────────────────────────────────────────┐ +│ 사용자 (화면 사용) │ +└──────────────────────────────────────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 1. 로그인 (일반 사용자) │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 2. 메뉴에서 화면 선택 │ + │ 사이드바 → 제품 관리 │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 3. 화면 렌더링 │ + │ /screens/[screenId] │ + │ InteractiveScreenViewer │ + └─────────────────────────────┘ + │ + ↓ + ┌─────────────────────────────┐ + │ 4. 데이터 조회/등록/수정 │ + │ - 테이블에서 행 선택 │ + │ - 폼에 데이터 입력 │ + │ - [등록]/[수정]/[삭제] 버튼│ + └─────────────────────────────┘ +``` + +### 12.2 관리자 워크플로우 (화면 생성) + +#### 단계 1: 화면 디자이너 접속 + +``` +/admin/screenMng/screenMngList + ↓ +[새 화면 만들기] 버튼 클릭 + ↓ +ScreenDesigner 컴포넌트 로드 +``` + +#### 단계 2: 화면 디자인 + +**2-1. 기본 정보 설정** + +``` +화면 정보 입력: +- 화면 코드: PRODUCT_MGMT +- 화면명: 제품 관리 +- 테이블명: product_info +- 화면 타입: form (단일 폼) | list (목록) +``` + +**2-2. 컴포넌트 배치** + +``` +좌측 팔레트에서 컴포넌트 선택 + ↓ +캔버스에 드래그&드롭 + ↓ +위치/크기 조정 (10px 단위 스냅) + ↓ +우측 속성 패널에서 설정: + - 컬럼명: product_name + - 라벨: 제품명 + - 필수: ☑ + - 웹타입: text +``` + +**2-3. 버튼 액션 설정** + +``` +버튼 컴포넌트 추가 + ↓ +액션 타입 선택: + - 데이터 저장 (POST) + - 데이터 수정 (PUT) + - 데이터 삭제 (DELETE) + - 데이터플로우 실행 + - 외부 API 호출 + - 화면 이동 + ↓ +액션 설정: + - 대상 테이블: product_info + - 성공 시: 목록 새로고침 + - 실패 시: 에러 메시지 표시 +``` + +**2-4. 다국어 설정** + +``` +다국어 탭 클릭 + ↓ +컴포넌트별 다국어 입력: + - 한국어(KR): 제품명 + - 영어(EN): Product Name + - 중국어(CN): 产品名称 +``` + +**2-5. 저장** + +``` +[저장] 버튼 클릭 + ↓ +POST /api/screen-management/screens + ↓ +화면 정의 JSON 저장 (DB: screen_definition) +``` + +#### 단계 3: 메뉴에 화면 할당 + +``` +/admin/menu + ↓ +메뉴 트리에서 위치 선택 + ↓ +[화면 할당] 버튼 클릭 + ↓ +방금 생성한 화면 선택 (PRODUCT_MGMT) + ↓ +저장 → menu_screen 테이블에 연결 +``` + +#### 단계 4: 권한 부여 + +``` +/admin/userMng/userAuthList + ↓ +사용자 선택 + ↓ +메뉴 권한 설정 + ↓ +"제품 관리" 메뉴 체크 + ↓ +저장 → user_menu_auth 테이블 +``` + +### 12.3 사용자 워크플로우 (화면 사용) + +#### 단계 1: 로그인 + +``` +/login + ↓ +사용자 ID/PW 입력 + ↓ +POST /api/auth/login + ↓ +JWT 토큰 발급 + ↓ +메인 페이지 (/main) +``` + +#### 단계 2: 메뉴 선택 + +``` +좌측 사이드바 메뉴 트리 + ↓ +"제품 관리" 메뉴 클릭 + ↓ +MenuContext에서 메뉴 정보 조회: + - menuId, screenId, screenCode + ↓ +화면 뷰어로 이동 (/screens/[screenId]) +``` + +#### 단계 3: 화면 렌더링 + +``` +InteractiveScreenViewer 컴포넌트 로드 + ↓ +1. GET /api/screen-management/screens/[screenId] + → 화면 정의 JSON 조회 + ↓ +2. GET /api/data/product_info?page=1&size=20 + → 데이터 조회 + ↓ +3. DynamicComponentRenderer로 컴포넌트 렌더링 + ↓ +4. 폼 데이터 바인딩 (formData 상태) +``` + +#### 단계 4: 데이터 조작 + +**4-1. 신규 등록** + +``` +[등록] 버튼 클릭 + ↓ +빈 폼 표시 (ScreenModal 또는 EditModal) + ↓ +사용자가 데이터 입력: + - 제품명: "제품A" + - 가격: 10000 + ↓ +[저장] 버튼 클릭 + ↓ +버튼 액션 실행: + - POST /api/data/product_info + - Body: { product_name: "제품A", price: 10000 } + ↓ +성공 응답 + ↓ +목록 새로고침 (React Query invalidateQueries) +``` + +**4-2. 수정** + +``` +테이블에서 행 선택 (클릭) + ↓ +선택된 데이터 → selectedRowsData 상태 업데이트 + ↓ +[수정] 버튼 클릭 + ↓ +폼에 기존 데이터 표시 (EditModal) + ↓ +사용자가 데이터 수정: + - 가격: 10000 → 12000 + ↓ +[저장] 버튼 클릭 + ↓ +버튼 액션 실행: + - PUT /api/data/product_info/123 + - Body: { price: 12000 } + ↓ +목록 새로고침 +``` + +**4-3. 삭제** + +``` +테이블에서 행 선택 + ↓ +[삭제] 버튼 클릭 + ↓ +확인 다이얼로그 표시 + ↓ +확인 클릭 + ↓ +버튼 액션 실행: + - DELETE /api/data/product_info/123 + ↓ +목록 새로고침 +``` + +### 12.4 데이터플로우 실행 워크플로우 + +``` +1. 관리자가 데이터플로우 정의 + /admin/systemMng/dataflow + ↓ +2. 화면에 버튼 추가 & 플로우 연결 + 버튼 액션: "데이터플로우 실행" + 플로우 ID: flow_123 + ↓ +3. 사용자가 버튼 클릭 + ↓ +4. 프론트엔드: POST /api/dataflow/execute + Body: { flowId: 123, inputData: {...} } + ↓ +5. 백엔드: 플로우 실행 + - 노드 순회 + - 조건 분기 + - 외부 API 호출 + - 데이터 변환 + ↓ +6. 결과 반환 + ↓ +7. 프론트엔드: 결과 표시 (toast 또는 화면 갱신) +``` + +--- + +## 13. 요약 & 핵심 포인트 + +### 13.1 아키텍처 핵심 + +1. **Next.js 14 App Router** - 라우트 그룹 기반 구조화 +2. **컴포넌트 레지스트리** - 동적 등록/렌더링 시스템 +3. **V2 통합 시스템** - 9개 통합 컴포넌트로 단순화 +4. **화면 디자이너** - 노코드 화면 생성 도구 +5. **API 클라이언트** - Axios 기반 통일된 API 호출 +6. **다국어 지원** - DB 기반 동적 다국어 +7. **JWT 인증** - 토큰 기반 인증/세션 관리 + +### 13.2 파일 통계 + +| 항목 | 개수 | +|------|------| +| 총 파일 수 | 1,480개 | +| TypeScript/TSX | 1,395개 (946 tsx + 449 ts) | +| Markdown 문서 | 63개 | +| CSS | 22개 | +| 페이지 (라우트) | 76개 | +| 컴포넌트 | 500개+ | +| API 클라이언트 | 57개 | +| Context | 12개 | +| Custom Hooks | 32개 | +| 타입 정의 | 44개 | + +### 13.3 주요 경로 + +``` +화면 디자이너: /admin/screenMng/screenMngList +화면 뷰어: /screens/[screenId] +메뉴 관리: /admin/menu +사용자 관리: /admin/userMng/userMngList +테이블 관리: /admin/systemMng/tableMngList +다국어 관리: /admin/systemMng/i18nList +데이터플로우: /admin/systemMng/dataflow +대시보드: /dashboard/[dashboardId] +``` + +### 13.4 기술 스택 + +| 분류 | 기술 | +|------|------| +| 프레임워크 | Next.js 14 | +| UI 라이브러리 | React 18 | +| 언어 | TypeScript (strict mode) | +| 스타일링 | Tailwind CSS + shadcn/ui | +| 상태 관리 | React Query + Zustand + Context API | +| HTTP 클라이언트 | Axios | +| 폼 검증 | Zod | +| 날짜 처리 | date-fns | +| 아이콘 | lucide-react | +| 알림 | sonner | + +--- + +## 마무리 + +이 문서는 WACE ERP 프론트엔드의 전체 아키텍처를 분석한 결과입니다. + +**핵심 인사이트:** +- ✅ 높은 수준의 모듈화 (레지스트리 시스템) +- ✅ 노코드 화면 디자이너 (관리자가 직접 화면 생성) +- ✅ V2 통합 컴포넌트 시스템 (개발 효율성 향상) +- ✅ 동적 다국어 지원 (DB 기반) +- ✅ 완전한 TypeScript 타입 안정성 + +**개선 기회:** +- 일부 레거시 컴포넌트의 V2 마이그레이션 +- 테스트 코드 추가 (현재 거의 없음) +- 성능 최적화 (코드 스플리팅, 레이지 로딩) + +--- + +**작성자 노트** + +야... 진짜 엄청난 프로젝트네. 파일이 1,500개가 넘고 컴포넌트만 500개야. 특히 화면 디자이너가 7,000줄이 넘는 거 보고 놀랐어. 이 정도 규모면 엔터프라이즈급 ERP 시스템이라고 봐도 되겠어. + +가장 인상적이었던 건 **레지스트리 시스템**이랑 **V2 통합 아키텍처**야. 컴포넌트를 동적으로 등록하고 렌더링하는 구조가 꽤 잘 설계되어 있어. 다만 레거시 코드가 아직 많이 남아있어서 V2로 완전히 전환하면 코드 베이스가 훨씬 깔끔해질 것 같아. + +어쨌든 분석하느라 꽤 걸렸는데, 이 문서가 전체 워크플로우 문서 작성하는 데 도움이 되면 좋겠어! 🎉 diff --git a/mcp-agent-orchestrator/src/index.ts b/mcp-agent-orchestrator/src/index.ts index 3634cf70..51b372cd 100644 --- a/mcp-agent-orchestrator/src/index.ts +++ b/mcp-agent-orchestrator/src/index.ts @@ -16,15 +16,12 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; -import { exec } from "child_process"; -import { promisify } from "util"; +import { spawn } from "child_process"; import { platform } from "os"; import { AGENT_CONFIGS } from "./agents/prompts.js"; import { AgentType, ParallelResult } from "./agents/types.js"; import { logger } from "./utils/logger.js"; -const execAsync = promisify(exec); - // OS 감지 const isWindows = platform() === "win32"; logger.info(`Platform detected: ${platform()} (isWindows: ${isWindows})`); @@ -46,9 +43,11 @@ const server = new Server( * Cursor Agent CLI를 통해 에이전트 호출 * Cursor Team Plan 사용 - API 키 불필요! * + * spawn + stdin 직접 전달 방식으로 쉘 이스케이프 문제 완전 해결 + * * 크로스 플랫폼 지원: - * - Windows: cmd /c "echo. | agent ..." (stdin 닫기 위해) - * - Mac/Linux: ~/.local/bin/agent 사용 + * - Windows: agent (PATH에서 검색) + * - Mac/Linux: ~/.local/bin/agent */ async function callAgentCLI( agentType: AgentType, @@ -60,56 +59,90 @@ async function callAgentCLI( // 모델 선택: PM은 opus, 나머지는 sonnet const model = agentType === 'pm' ? 'opus-4.5' : 'sonnet-4.5'; - logger.info(`Calling ${agentType} agent via CLI`, { model, task: task.substring(0, 100) }); + logger.info(`Calling ${agentType} agent via CLI (spawn)`, { model, task: task.substring(0, 100) }); - try { - const userMessage = context - ? `${task}\n\n배경 정보:\n${context}` - : task; - - // 프롬프트를 임시 파일에 저장하여 쉘 이스케이프 문제 회피 - const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`; - - // Base64 인코딩으로 특수문자 문제 해결 - const encodedPrompt = Buffer.from(fullPrompt).toString('base64'); - - let cmd: string; - let shell: string; - const agentPath = isWindows ? 'agent' : `${process.env.HOME}/.local/bin/agent`; - - if (isWindows) { - // Windows: PowerShell을 통해 Base64 디코딩 후 실행 - cmd = `powershell -Command "$prompt = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${encodedPrompt}')); echo $prompt | ${agentPath} --model ${model} --print"`; - shell = 'powershell.exe'; - } else { - // Mac/Linux: echo로 base64 디코딩 후 파이프 - cmd = `echo "${encodedPrompt}" | base64 -d | ${agentPath} --model ${model} --print`; - shell = '/bin/bash'; - } - - logger.debug(`Executing: ${agentPath} --model ${model} --print`); - - const { stdout, stderr } = await execAsync(cmd, { + const userMessage = context + ? `${task}\n\n배경 정보:\n${context}` + : task; + + const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`; + const agentPath = isWindows ? 'agent' : `${process.env.HOME}/.local/bin/agent`; + + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + let settled = false; + + const child = spawn(agentPath, ['--model', model, '--print'], { cwd: process.cwd(), - maxBuffer: 10 * 1024 * 1024, // 10MB buffer - timeout: 300000, // 5분 타임아웃 - shell, env: { ...process.env, PATH: `${process.env.HOME}/.local/bin:${process.env.PATH}`, }, + stdio: ['pipe', 'pipe', 'pipe'], }); - if (stderr && !stderr.includes('warning') && !stderr.includes('info')) { - logger.warn(`${agentType} agent stderr`, { stderr: stderr.substring(0, 500) }); - } + child.stdout.on('data', (data: Buffer) => { + stdout += data.toString(); + }); - logger.info(`${agentType} agent completed via CLI`); - return stdout.trim(); - } catch (error) { - logger.error(`${agentType} agent CLI error`, error); - throw error; - } + child.stderr.on('data', (data: Buffer) => { + stderr += data.toString(); + }); + + child.on('error', (err: Error) => { + if (!settled) { + settled = true; + logger.error(`${agentType} agent spawn error`, err); + reject(err); + } + }); + + child.on('close', (code: number | null) => { + if (settled) return; + settled = true; + + if (stderr) { + // 경고/정보 레벨 stderr는 무시 + const significantStderr = stderr + .split('\n') + .filter((line: string) => line && !line.includes('warning') && !line.includes('info') && !line.includes('debug')) + .join('\n'); + if (significantStderr) { + logger.warn(`${agentType} agent stderr`, { stderr: significantStderr.substring(0, 500) }); + } + } + + if (code === 0 || stdout.trim().length > 0) { + // 정상 종료이거나, 에러 코드여도 stdout에 결과가 있으면 성공 처리 + logger.info(`${agentType} agent completed via CLI (exit code: ${code})`); + resolve(stdout.trim()); + } else { + const errorMsg = `Agent exited with code ${code}. stderr: ${stderr.substring(0, 1000)}`; + logger.error(`${agentType} agent CLI error`, { code, stderr: stderr.substring(0, 1000) }); + reject(new Error(errorMsg)); + } + }); + + // 타임아웃 (5분) + const timeout = setTimeout(() => { + if (!settled) { + settled = true; + child.kill('SIGTERM'); + logger.error(`${agentType} agent timed out after 5 minutes`); + reject(new Error(`${agentType} agent timed out after 5 minutes`)); + } + }, 300000); + + // 프로세스 종료 시 타이머 클리어 + child.on('close', () => clearTimeout(timeout)); + + // stdin으로 프롬프트 직접 전달 (쉘 이스케이프 문제 없음!) + child.stdin.write(fullPrompt); + child.stdin.end(); + + logger.debug(`Prompt sent to ${agentType} agent via stdin (${fullPrompt.length} chars)`); + }); } /**