ERP-node/docs/plan-multi-table-excel-uplo...

6.6 KiB

다중 테이블 엑셀 업로드 범용 시스템

개요

하나의 플랫 엑셀 파일로 계층적 다중 테이블(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)

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[];     // 이 모드에서 활성화되는 레벨 인덱스
  }>;
}

거래처 관리 설정 예시

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단계: 결과 반환

{
  "success": true,
  "results": {
    "customer_mng": { "inserted": 2, "updated": 1 },
    "customer_item_mapping": { "inserted": 5, "updated": 2 },
    "customer_item_prices": { "inserted": 12 }
  },
  "errors": []
}

테스트 계획

1단계: 백엔드 서비스

  • 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단계: 통합

  • 거래처 관리 화면에 연결
  • 실제 데이터로 테스트

진행 상태

  • 완료된 테스트는 로 표시
  • 현재 진행 중인 테스트는 [진행중]으로 표시