# 다중 테이블 엑셀 업로드 범용 시스템 ## 개요 하나의 플랫 엑셀 파일로 계층적 다중 테이블(2~N개)에 데이터를 일괄 등록하는 범용 시스템. 거래처 관리(customer_mng → customer_item_mapping → customer_item_prices)를 첫 번째 적용 대상으로 하되, 공급업체, BOM 등 다른 화면에서도 재사용 가능하도록 설계한다. ## 핵심 기능 1. 모드 선택: 어느 레벨까지 등록할지 사용자가 선택 2. 템플릿 다운로드: 모드에 맞는 엑셀 양식 자동 생성 3. 파일 업로드: 플랫 엑셀 → 계층 그룹핑 → 트랜잭션 UPSERT 4. 컬럼 매핑: 엑셀 헤더 ↔ DB 컬럼 자동/수동 매핑 ## DB 테이블 관계 (거래처 관리) ``` customer_mng (Level 1 - 루트) PK: id (SERIAL) UNIQUE: customer_code └─ customer_item_mapping (Level 2) PK: id (UUID) FK: customer_id → customer_mng.id UPSERT키: customer_id + customer_item_code └─ customer_item_prices (Level 3) PK: id (UUID) FK: mapping_id → customer_item_mapping.id 항상 INSERT (기간별 단가 이력) ``` ## 범용 설정 구조 (TableChainConfig) ```typescript interface TableLevel { tableName: string; label: string; // 부모와의 관계 parentFkColumn?: string; // 이 테이블에서 부모를 참조하는 FK 컬럼 parentRefColumn?: string; // 부모 테이블에서 참조되는 컬럼 (PK 또는 UNIQUE) // UPSERT 설정 upsertMode: 'upsert' | 'insert'; // upsert: 기존 데이터 있으면 UPDATE, insert: 항상 신규 upsertKeyColumns?: string[]; // UPSERT 매칭 키 (예: ['customer_code']) // 엑셀 매핑 컬럼 columns: Array<{ dbColumn: string; excelHeader: string; required: boolean; defaultValue?: any; }>; } interface TableChainConfig { id: string; name: string; description: string; levels: TableLevel[]; // 0 = 루트, 1 = 자식, 2 = 손자... uploadModes: Array<{ id: string; label: string; description: string; activeLevels: number[]; // 이 모드에서 활성화되는 레벨 인덱스 }>; } ``` ## 거래처 관리 설정 예시 ```typescript const customerChainConfig: TableChainConfig = { id: 'customer_management', name: '거래처 관리', description: '거래처, 품목매핑, 단가 일괄 등록', levels: [ { tableName: 'customer_mng', label: '거래처', upsertMode: 'upsert', upsertKeyColumns: ['customer_code'], columns: [ { dbColumn: 'customer_code', excelHeader: '거래처코드', required: true }, { dbColumn: 'customer_name', excelHeader: '거래처명', required: true }, { dbColumn: 'division', excelHeader: '구분', required: false }, { dbColumn: 'contact_person', excelHeader: '담당자', required: false }, { dbColumn: 'contact_phone', excelHeader: '연락처', required: false }, { dbColumn: 'email', excelHeader: '이메일', required: false }, { dbColumn: 'business_number', excelHeader: '사업자번호', required: false }, { dbColumn: 'address', excelHeader: '주소', required: false }, ], }, { tableName: 'customer_item_mapping', label: '품목매핑', parentFkColumn: 'customer_id', parentRefColumn: 'id', upsertMode: 'upsert', upsertKeyColumns: ['customer_id', 'customer_item_code'], columns: [ { dbColumn: 'customer_item_code', excelHeader: '거래처품번', required: true }, { dbColumn: 'customer_item_name', excelHeader: '거래처품명', required: true }, { dbColumn: 'item_id', excelHeader: '품목ID', required: false }, ], }, { tableName: 'customer_item_prices', label: '단가', parentFkColumn: 'mapping_id', parentRefColumn: 'id', upsertMode: 'insert', columns: [ { dbColumn: 'base_price', excelHeader: '기준단가', required: true }, { dbColumn: 'discount_type', excelHeader: '할인유형', required: false }, { dbColumn: 'discount_value', excelHeader: '할인값', required: false }, { dbColumn: 'start_date', excelHeader: '적용시작일', required: false }, { dbColumn: 'end_date', excelHeader: '적용종료일', required: false }, { dbColumn: 'currency_code', excelHeader: '통화', required: false }, ], }, ], uploadModes: [ { id: 'customer_only', label: '거래처만 등록', description: '거래처 기본정보만', activeLevels: [0] }, { id: 'customer_item', label: '거래처 + 품목정보', description: '거래처와 품목매핑', activeLevels: [0, 1] }, { id: 'customer_item_price', label: '거래처 + 품목 + 단가', description: '전체 등록', activeLevels: [0, 1, 2] }, ], }; ``` ## 처리 로직 (백엔드) ### 1단계: 그룹핑 엑셀의 플랫 행을 계층별 그룹으로 변환: - Level 0 (거래처): customer_code 기준 그룹핑 - Level 1 (품목매핑): customer_code + customer_item_code 기준 그룹핑 - Level 2 (단가): 매 행마다 INSERT ### 2단계: 계단식 UPSERT (트랜잭션) ``` BEGIN TRANSACTION FOR EACH unique customer_code: 1. customer_mng UPSERT → 결과에서 id 획득 (returnedId) FOR EACH unique customer_item_code (해당 거래처): 2. customer_item_mapping의 customer_id = returnedId 주입 UPSERT → 결과에서 id 획득 (mappingId) FOR EACH price row (해당 품목매핑): 3. customer_item_prices의 mapping_id = mappingId 주입 INSERT COMMIT (전체 성공) or ROLLBACK (하나라도 실패) ``` ### 3단계: 결과 반환 ```json { "success": true, "results": { "customer_mng": { "inserted": 2, "updated": 1 }, "customer_item_mapping": { "inserted": 5, "updated": 2 }, "customer_item_prices": { "inserted": 12 } }, "errors": [] } ``` ## 테스트 계획 ### 1단계: 백엔드 서비스 - [x] plan.md 작성 - [ ] multiTableExcelService.ts 기본 구조 작성 - [ ] 그룹핑 로직 구현 - [ ] 계단식 UPSERT 로직 구현 - [ ] 트랜잭션 처리 - [ ] 에러 핸들링 ### 2단계: API 엔드포인트 - [ ] POST /api/data/multi-table/upload 추가 - [ ] POST /api/data/multi-table/template 추가 (템플릿 다운로드) - [ ] 입력값 검증 ### 3단계: 프론트엔드 - [ ] MultiTableExcelUploadModal.tsx 컴포넌트 작성 - [ ] 모드 선택 UI - [ ] 템플릿 다운로드 버튼 - [ ] 파일 업로드 + 미리보기 - [ ] 컬럼 매핑 UI - [ ] 업로드 결과 표시 ### 4단계: 통합 - [ ] 거래처 관리 화면에 연결 - [ ] 실제 데이터로 테스트 ## 진행 상태 - 완료된 테스트는 [x]로 표시 - 현재 진행 중인 테스트는 [진행중]으로 표시