ERP-node/docs/DDD1542/본서버_개발서버_마이그레이션_상세가이드.md

17 KiB

본서버 → 개발서버 마이그레이션 가이드

개요

본 문서는 **본서버(Production)**의 screen_layouts (V1) 데이터를 **개발서버(Development)**의 screen_layouts_v2 시스템으로 마이그레이션하는 절차를 정의합니다.

마이그레이션 방향

본서버 (Production)          개발서버 (Development)
┌─────────────────────┐      ┌─────────────────────┐
│ screen_layouts (V1) │  →   │ screen_layouts_v2   │
│ - 컴포넌트별 레코드  │      │ - 화면당 1개 레코드  │
│ - properties JSONB  │      │ - layout_data JSONB │
└─────────────────────┘      └─────────────────────┘

최종 목표

개발서버에서 완성 후 개발서버 → 본서버로 배포


1. V1 vs V2 구조 차이

1.1 screen_layouts (V1) - 본서버

-- 컴포넌트별 1개 레코드
CREATE TABLE screen_layouts (
  layout_id SERIAL PRIMARY KEY,
  screen_id INTEGER,
  component_type VARCHAR(50),
  component_id VARCHAR(100),
  properties JSONB,  -- 모든 설정값 포함
  ...
);

특징:

  • 화면당 N개 레코드 (컴포넌트 수만큼)
  • properties에 모든 설정 저장 (defaults + overrides 구분 없음)
  • menu_objid 기반 채번/카테고리 관리

1.2 screen_layouts_v2 - 개발서버

-- 화면당 1개 레코드
CREATE TABLE screen_layouts_v2 (
  layout_id SERIAL PRIMARY KEY,
  screen_id INTEGER NOT NULL,
  company_code VARCHAR(20) NOT NULL,
  layout_data JSONB NOT NULL DEFAULT '{}'::jsonb,
  UNIQUE(screen_id, company_code)
);

layout_data 구조:

{
  "version": "2.0",
  "components": [
    {
      "id": "comp_xxx",
      "url": "@/lib/registry/components/v2-table-list",
      "position": { "x": 0, "y": 0 },
      "size": { "width": 100, "height": 50 },
      "displayOrder": 0,
      "overrides": {
        "tableName": "inspection_standard",
        "columns": ["id", "name"]
      }
    }
  ],
  "updatedAt": "2026-02-03T12:00:00Z"
}

특징:

  • 화면당 1개 레코드
  • url + overrides 방식 (Zod 스키마 defaults와 병합)
  • table_name + column_name 기반 채번/카테고리 관리 (전역)

2. 데이터 타입 관리 구조 (V2)

2.1 핵심 테이블 관계

table_type_columns (컬럼 타입 정의)
├── input_type = 'category' → category_values
├── input_type = 'numbering' → numbering_rules
└── input_type = 'text', 'date', 'number', etc.

2.2 table_type_columns

각 테이블의 컬럼별 입력 타입을 정의합니다.

SELECT table_name, column_name, input_type, column_label
FROM table_type_columns
WHERE input_type IN ('category', 'numbering');

주요 input_type:

input_type 설명 연결 테이블
text 텍스트 입력 -
number 숫자 입력 -
date 날짜 입력 -
category 카테고리 드롭다운 category_values
numbering 자동 채번 numbering_rules
entity 엔티티 검색 -

2.3 category_values (카테고리 관리)

-- 카테고리 값 조회
SELECT value_id, table_name, column_name, value_code, value_label, 
       parent_value_id, depth, company_code
FROM category_values
WHERE table_name = 'inspection_standard'
  AND column_name = 'inspection_method'
  AND company_code = 'COMPANY_7';

V1 vs V2 차이:

구분 V1 V2
menu_objid table_name + column_name
범위 화면별 전역 (테이블.컬럼별)
계층 단일 3단계 (대/중/소분류)

2.4 numbering_rules (채번 규칙)

-- 채번 규칙 조회
SELECT rule_id, rule_name, table_name, column_name, separator, 
       reset_period, current_sequence, company_code
FROM numbering_rules
WHERE company_code = 'COMPANY_7';

연결 방식:

table_type_columns.detail_settings = '{"numberingRuleId": "rule-xxx"}'
                                              ↓
                               numbering_rules.rule_id = "rule-xxx"

3. 컴포넌트 매핑

3.1 기본 컴포넌트 매핑

V1 (본서버) V2 (개발서버) 비고
table-list v2-table-list 테이블 목록
button-primary v2-button-primary 버튼
text-input v2-text-input 텍스트 입력
select-basic v2-select 드롭다운
date-input v2-date-input 날짜 입력
entity-search-input v2-entity-search 엔티티 검색
tabs-widget v2-tabs-widget

3.2 특수 컴포넌트 매핑

V1 (본서버) V2 (개발서버) 마이그레이션 방식
category-manager v2-category-manager table_name 기반으로 변경
numbering-rule v2-numbering-rule table_name 기반으로 변경
모달 화면 overlay 통합 부모 화면에 통합

3.3 모달 처리 방식 변경

V1 (본서버):

화면 A (screen_id: 142) - 검사장비관리
    └── 버튼 클릭 → 화면 B (screen_id: 143) - 검사장비 등록모달

V2 (개발서버):

화면 A (screen_id: 142) - 검사장비관리
    └── v2-dialog-form 컴포넌트로 모달 통합

4. 마이그레이션 절차

4.1 사전 분석

-- 1. 본서버 화면 목록 확인
SELECT sd.screen_id, sd.screen_code, sd.screen_name, sd.table_name,
       COUNT(sl.layout_id) as component_count
FROM screen_definitions sd
LEFT JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
WHERE sd.screen_code LIKE 'COMPANY_7_%'
  AND sd.screen_name LIKE '%품질%'
GROUP BY sd.screen_id, sd.screen_code, sd.screen_name, sd.table_name;

-- 2. 개발서버 V2 화면 현황 확인
SELECT sd.screen_id, sd.screen_code, sd.screen_name,
       sv2.layout_data IS NOT NULL as has_v2_layout
FROM screen_definitions sd
LEFT JOIN screen_layouts_v2 sv2 ON sd.screen_id = sv2.screen_id
WHERE sd.company_code = 'COMPANY_7';

4.2 Step 1: screen_definitions 동기화

-- 본서버에만 있는 화면을 개발서버에 추가
INSERT INTO screen_definitions (screen_code, screen_name, table_name, company_code, ...)
SELECT screen_code, screen_name, table_name, company_code, ...
FROM [본서버].screen_definitions
WHERE screen_code NOT IN (SELECT screen_code FROM screen_definitions);

4.3 Step 2: V1 → V2 레이아웃 변환

// 변환 로직 (pseudo-code)
async function convertV1toV2(screenId: number, companyCode: string) {
  // 1. V1 레이아웃 조회
  const v1Layouts = await getV1Layouts(screenId);
  
  // 2. V2 형식으로 변환
  const v2Layout = {
    version: "2.0",
    components: v1Layouts.map(v1 => ({
      id: v1.component_id,
      url: mapComponentUrl(v1.component_type),
      position: { x: v1.position_x, y: v1.position_y },
      size: { width: v1.width, height: v1.height },
      displayOrder: v1.display_order,
      overrides: extractOverrides(v1.properties)
    })),
    updatedAt: new Date().toISOString()
  };
  
  // 3. V2 테이블에 저장
  await saveV2Layout(screenId, companyCode, v2Layout);
}

function mapComponentUrl(v1Type: string): string {
  const mapping = {
    'table-list': '@/lib/registry/components/v2-table-list',
    'button-primary': '@/lib/registry/components/v2-button-primary',
    'category-manager': '@/lib/registry/components/v2-category-manager',
    'numbering-rule': '@/lib/registry/components/v2-numbering-rule',
    // ... 기타 매핑
  };
  return mapping[v1Type] || `@/lib/registry/components/v2-${v1Type}`;
}

4.4 Step 3: 카테고리 데이터 마이그레이션

-- 본서버 카테고리 데이터 → 개발서버 category_values
INSERT INTO category_values (
  table_name, column_name, value_code, value_label, 
  value_order, parent_value_id, depth, company_code
)
SELECT 
  -- V1 카테고리 데이터를 table_name + column_name 기반으로 변환
  'inspection_standard' as table_name,
  'inspection_method' as column_name,
  value_code,
  value_label,
  sort_order,
  NULL as parent_value_id,
  1 as depth,
  'COMPANY_7' as company_code
FROM [본서버_카테고리_데이터];

4.5 Step 4: 채번 규칙 마이그레이션

-- 본서버 채번 규칙 → 개발서버 numbering_rules
INSERT INTO numbering_rules (
  rule_id, rule_name, table_name, column_name,
  separator, reset_period, current_sequence, company_code
)
SELECT 
  rule_id,
  rule_name,
  'inspection_standard' as table_name,
  'inspection_code' as column_name,
  separator,
  reset_period,
  0 as current_sequence,  -- 시퀀스 초기화
  'COMPANY_7' as company_code
FROM [본서버_채번_규칙];

4.6 Step 5: table_type_columns 설정

-- 카테고리 컬럼 설정
UPDATE table_type_columns
SET input_type = 'category'
WHERE table_name = 'inspection_standard'
  AND column_name = 'inspection_method'
  AND company_code = 'COMPANY_7';

-- 채번 컬럼 설정
UPDATE table_type_columns
SET 
  input_type = 'numbering',
  detail_settings = '{"numberingRuleId": "rule-xxx"}'
WHERE table_name = 'inspection_standard'
  AND column_name = 'inspection_code'
  AND company_code = 'COMPANY_7';

5. 품질관리 메뉴 마이그레이션 현황

5.1 화면 매핑 현황

본서버 코드 화면명 테이블 개발서버 상태 비고
COMPANY_7_126 검사정보 관리 inspection_standard V2 존재 컴포넌트 수 확인 필요
COMPANY_7_127 품목옵션 설정 - V2 존재 v2-category-manager 사용중
COMPANY_7_138 카테고리 설정 inspection_standard 누락 V2: table_name 기반으로 변경
COMPANY_7_139 코드 설정 inspection_standard 누락 V2: table_name 기반으로 변경
COMPANY_7_142 검사장비 관리 inspection_equipment_mng 누락 모달 통합 필요
COMPANY_7_143 검사장비 등록모달 inspection_equipment_mng 누락 COMPANY_7_142에 통합
COMPANY_7_144 불량기준 정보 defect_standard_mng 누락 모달 통합 필요
COMPANY_7_145 불량기준 등록모달 defect_standard_mng 누락 COMPANY_7_144에 통합

5.2 카테고리/채번 컬럼 현황

inspection_standard:

컬럼 input_type 라벨
inspection_method category 검사방법
unit category 단위
apply_type category 적용구분
inspection_type category 유형

inspection_equipment_mng:

컬럼 input_type 라벨
equipment_type category 장비유형
installation_location category 설치장소
equipment_status category 장비상태

defect_standard_mng:

컬럼 input_type 라벨
defect_type category 불량유형
severity category 심각도
inspection_type category 검사유형

6. 자동화 스크립트

6.1 마이그레이션 실행 스크립트

// backend-node/src/scripts/migrateV1toV2.ts
import { getPool } from "../database/db";

interface MigrationResult {
  screenCode: string;
  success: boolean;
  message: string;
  componentCount?: number;
}

async function migrateScreenToV2(
  screenCode: string,
  companyCode: string
): Promise<MigrationResult> {
  const pool = getPool();
  
  try {
    // 1. V1 레이아웃 조회 (본서버에서)
    const v1Result = await pool.query(`
      SELECT sl.*, sd.table_name, sd.screen_name
      FROM screen_layouts sl
      JOIN screen_definitions sd ON sl.screen_id = sd.screen_id
      WHERE sd.screen_code = $1
      ORDER BY sl.display_order
    `, [screenCode]);
    
    if (v1Result.rows.length === 0) {
      return { screenCode, success: false, message: "V1 레이아웃 없음" };
    }
    
    // 2. V2 형식으로 변환
    const components = v1Result.rows
      .filter(row => row.component_type !== '_metadata')
      .map(row => ({
        id: row.component_id || `comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        url: mapComponentUrl(row.component_type),
        position: { x: row.position_x || 0, y: row.position_y || 0 },
        size: { width: row.width || 100, height: row.height || 50 },
        displayOrder: row.display_order || 0,
        overrides: extractOverrides(row.properties, row.component_type)
      }));
    
    const layoutData = {
      version: "2.0",
      components,
      migratedFrom: "V1",
      migratedAt: new Date().toISOString()
    };
    
    // 3. 개발서버 V2 테이블에 저장
    const screenId = v1Result.rows[0].screen_id;
    
    await pool.query(`
      INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data)
      VALUES ($1, $2, $3)
      ON CONFLICT (screen_id, company_code) 
      DO UPDATE SET layout_data = $3, updated_at = NOW()
    `, [screenId, companyCode, JSON.stringify(layoutData)]);
    
    return {
      screenCode,
      success: true,
      message: "마이그레이션 완료",
      componentCount: components.length
    };
  } catch (error: any) {
    return { screenCode, success: false, message: error.message };
  }
}

function mapComponentUrl(v1Type: string): string {
  const mapping: Record<string, string> = {
    'table-list': '@/lib/registry/components/v2-table-list',
    'button-primary': '@/lib/registry/components/v2-button-primary',
    'text-input': '@/lib/registry/components/v2-text-input',
    'select-basic': '@/lib/registry/components/v2-select',
    'date-input': '@/lib/registry/components/v2-date-input',
    'entity-search-input': '@/lib/registry/components/v2-entity-search',
    'category-manager': '@/lib/registry/components/v2-category-manager',
    'numbering-rule': '@/lib/registry/components/v2-numbering-rule',
    'tabs-widget': '@/lib/registry/components/v2-tabs-widget',
    'textarea-basic': '@/lib/registry/components/v2-textarea',
  };
  return mapping[v1Type] || `@/lib/registry/components/v2-${v1Type}`;
}

function extractOverrides(properties: any, componentType: string): Record<string, any> {
  if (!properties) return {};
  
  // V2 Zod 스키마 defaults와 비교하여 다른 값만 추출
  // (실제 구현 시 각 컴포넌트의 defaultConfig와 비교)
  const overrides: Record<string, any> = {};
  
  // 필수 설정만 추출
  if (properties.tableName) overrides.tableName = properties.tableName;
  if (properties.columns) overrides.columns = properties.columns;
  if (properties.label) overrides.label = properties.label;
  if (properties.onClick) overrides.onClick = properties.onClick;
  
  return overrides;
}

7. 검증 체크리스트

7.1 마이그레이션 전

  • 본서버 화면 목록 확인
  • 개발서버 기존 V2 데이터 백업
  • 컴포넌트 매핑 테이블 검토
  • 카테고리/채번 데이터 분석

7.2 마이그레이션 후

  • screen_definitions 동기화 확인
  • screen_layouts_v2 데이터 생성 확인
  • 컴포넌트 렌더링 테스트
  • 카테고리 드롭다운 동작 확인
  • 채번 규칙 동작 확인
  • 저장/수정/삭제 기능 테스트

7.3 모달 통합 확인

  • 기존 모달 화면 → overlay 통합 완료
  • 부모-자식 데이터 연동 확인
  • 모달 열기/닫기 동작 확인

8. 롤백 계획

마이그레이션 실패 시 롤백 절차:

-- 1. V2 레이아웃 롤백
DELETE FROM screen_layouts_v2 
WHERE screen_id IN (
  SELECT screen_id FROM screen_definitions 
  WHERE screen_code LIKE 'COMPANY_7_%'
);

-- 2. 추가된 screen_definitions 롤백
DELETE FROM screen_definitions
WHERE screen_code IN ('신규_추가된_코드들')
  AND company_code = 'COMPANY_7';

-- 3. category_values 롤백
DELETE FROM category_values
WHERE company_code = 'COMPANY_7'
  AND created_at > '[마이그레이션_시작_시간]';

-- 4. numbering_rules 롤백
DELETE FROM numbering_rules
WHERE company_code = 'COMPANY_7'
  AND created_at > '[마이그레이션_시작_시간]';

9. 참고 자료

관련 코드 파일

  • V2 Category Manager: frontend/lib/registry/components/v2-category-manager/
  • V2 Numbering Rule: frontend/lib/registry/components/v2-numbering-rule/
  • Category Service: backend-node/src/services/categoryTreeService.ts
  • Numbering Service: backend-node/src/services/numberingRuleService.ts

관련 문서


변경 이력

날짜 작성자 내용
2026-02-03 DDD1542 초안 작성