ERP-node/docs/screen-implementation-guide/01_master-data/item-info.md

53 KiB

품목정보 (Item Info)

Screen ID: /screens/140 메뉴 경로: 기준정보 > 품목정보 테이블: item_info


⚠️ 문서 사용 안내

이 문서는 "품목정보" 화면의 구현 예시입니다.

📌 중요: JSON 데이터는 참고용입니다!

이 문서에 포함된 JSON 설정(레이아웃, 컴포넌트 구성 등)은 품목정보 화면에 특화된 예시입니다.

다른 화면을 구현할 때:

  1. 이 JSON을 그대로 복사해서 사용하지 마세요
  2. 해당 화면의 테이블 구조를 먼저 분석하세요
  3. 화면의 요구사항과 기능을 파악하세요
  4. 분석 결과에 맞는 새로운 JSON 구조를 작성하세요

참고해야 할 항목

  • 문서 구조 및 작성 형식
  • V2 컴포넌트 종류 및 사용법
  • API 호출 방식 및 DB INSERT 절차
  • 컴포넌트 설정 패턴 (position, size, overrides 구조)

복사하면 안 되는 항목

  • 테이블명 (item_info → 해당 화면의 테이블로 변경)
  • 컬럼 설정 (해당 테이블의 컬럼에 맞게 작성)
  • 필드명 (fieldName, columnName 등)
  • 화면명, screen_code, company_code
  • screen_id, targetScreenId (동적 생성되는 값)

🚨 컴포넌트 부족 시 필수 명시 사항

화면 분석 결과, 현재 V2 컴포넌트로 구현이 불가능한 기능이 있을 경우:

  1. 문서에 "구현 불가 항목" 섹션을 반드시 추가
  2. 다음 형식으로 명시:
## 🚫 구현 불가 항목 (컴포넌트 개발 필요)

| 기능 | 필요한 컴포넌트 | 현재 상태 | 비고 |
|------|-----------------|-----------|------|
| 트리 구조 표시 | v2-tree-view | 미구현 | 계층형 데이터 표시 필요 |
| 드래그 앤 드롭 | v2-drag-drop | 미구현 | 순서 변경 기능 |
  1. 컴포넌트 개발 우선순위/중요도 명시

1. 테이블 선택 및 화면 구조

1.1 사용 테이블

테이블명 용도 비고
item_info 품목 기본정보 주 테이블

1.2 테이블 컬럼 정의 (실제 DB 기준)

컬럼명 표시명 타입 필수 설명
id ID varchar(500) PK UUID 자동 생성
item_number 품번코드 varchar(500) 품목 고유 코드
item_name 품명 varchar(500) 품목명
status 상태 varchar(500) 정상, 품절, 대기, 단종
size 규격 varchar(500) 규격 정보
material 재질 varchar(500) 재질 정보
inventory_unit 재고단위 varchar(500) EA, kg, L, Sheet, Box
weight 중량 varchar(500) 중량 값
unit 단위 varchar(500) g, kg, kg/L, t
image 이미지 varchar(500) 품목 이미지 경로
division 구분 varchar(500) 원자재, 중간재, 완제품, 포장재 (카테고리 코드)
type 유형 varchar(500) 용도별 유형
meno 메모 varchar(500) 비고 (오타: memo)
selling_price 판매가 varchar(500) 기본값 '0'
standard_price 기준가 varchar(500) 기본값 '0'
currency_code 통화코드 varchar(500) 기본값 'KRW'
writer 등록자 varchar(500) 작성자 ID
company_code 회사코드 varchar(500) 멀티테넌시
created_date 등록일 timestamp 자동 생성
updated_date 수정일 timestamp 자동 갱신

1.3 화면 구조 개요

  • 화면 유형: 목록형 (단일 테이블 CRUD)
  • 주요 기능:
    • 품목 조회/검색/필터링
    • 품목 등록/수정/삭제
    • 그룹핑 (Group By)
    • 코드 변경/합병
    • 엑셀 업로드
    • 컬럼 표시/숨기기 설정

2. 컴포넌트 배치도

2.1 전체 레이아웃

┌─────────────────────────────────────────────────────────────────────────────┐
│ [검색 영역]                                                                 │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-search-widget                                                  │ │
│ │ ┌───────────┐ ┌───────────────┐ ┌───────────────┐ ┌─────────┐          │ │
│ │ │ 상태      │ │ 품번코드      │ │ 품명          │ │ [검색]  │          │ │
│ │ │ (select)  │ │ (text)        │ │ (text)        │ │         │          │ │
│ │ └───────────┘ └───────────────┘ └───────────────┘ └─────────┘          │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────┤
│ [테이블 헤더 + 액션 버튼]                                                   │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ [코드변경][업로드][다운로드]                   [등록][복사][수정][삭제]  │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────┤
│ [데이터 테이블]                                                             │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list                                                           │ │
│ │ ┌──┬────┬────────┬────────┬──────┬──────┬────────┬─────┬─────┬────────┐ │ │
│ │ │☐ │상태│품번코드│품명    │규격  │재질  │재고단위│중량 │단위 │구분    │ │ │
│ │ ├──┼────┼────────┼────────┼──────┼──────┼────────┼─────┼─────┼────────┤ │ │
│ │ │☐ │정상│R_001   │테스트A │100mm │SUS304│EA      │1.5  │kg   │원자재  │ │ │
│ │ │☐ │대기│R_002   │테스트B │200mm │AL    │kg      │2.0  │kg   │완제품  │ │ │
│ │ └──┴────┴────────┴────────┴──────┴──────┴────────┴─────┴─────┴────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 사용 가능한 V2 컴포넌트 목록

📌 V2 컴포넌트 전체 목록 - 화면 구성 시 사용 가능한 컴포넌트

컴포넌트 ID 설명 카테고리
v2-input 텍스트, 숫자, 비밀번호, 이메일 등 입력 입력
v2-select 드롭다운, 콤보박스, 라디오, 체크박스 입력
v2-date 날짜/시간 입력 입력
v2-button-primary 버튼 액션
v2-table-list 테이블 리스트 (CRUD) 테이블
v2-table-search-widget 테이블 검색/필터 위젯 유틸리티
v2-pivot-grid 피벗 그리드 (다차원 분석) 테이블
v2-aggregation-widget 집계 위젯 위젯
v2-text-display 텍스트 표시 (읽기 전용) 표시
v2-card-display 카드 표시 표시
v2-divider-line 구분선 레이아웃
v2-section-card 섹션 카드 (그룹핑) 레이아웃
v2-section-paper 섹션 페이퍼 (그룹핑) 레이아웃
v2-split-panel-layout 분할 패널 레이아웃 레이아웃
v2-repeat-container 반복 컨테이너 레이아웃
v2-repeater 리피터 (동적 행) 레이아웃
v2-category-manager 카테고리 관리 특수
v2-numbering-rule 채번규칙 특수
v2-media 미디어 (이미지/영상) 미디어
v2-rack-structure 랙 구조 (창고) 특수
v2-location-swap-selector 위치 스왑 선택기 특수

2.3 이 화면에서 사용하는 컴포넌트

컴포넌트 타입 역할
v2-table-search-widget 검색 필터
v2-table-list 품목 데이터 테이블
v2-button-primary 코드변경
v2-button-primary 업로드 (엑셀)
v2-button-primary 다운로드 (엑셀)
v2-button-primary 등록 (모달 열기)
v2-button-primary 복사 (모달 열기)
v2-button-primary 수정 (모달 열기)
v2-button-primary 삭제
v2-input 모달 - 텍스트 입력 필드
v2-select 모달 - 선택 필드

3. 화면 디자이너 설정 가이드

3.1 v2-table-search-widget (검색 필터) 설정

  1. 좌측 컴포넌트 패널에서 v2-table-search-widget 드래그하여 화면 상단에 배치
  2. 대상 테이블로 아래에 배치할 테이블 리스트 선택

💡 참고: 검색 필터는 사용자가 런타임에서 원하는 필드를 직접 추가/삭제하여 사용할 수 있습니다. 별도의 필드 설정이 필요 없습니다.


3.2 v2-table-list (품목 테이블) 설정

Step 1: 컴포넌트 추가

  1. 좌측 컴포넌트 패널에서 v2-table-list 드래그하여 검색 필터 아래에 배치

Step 2: 데이터 소스 설정

설정 항목 설정 값
테이블 선택 item_info
자동 컬럼 생성 체크 (테이블 컬럼 자동 로드)

Step 3: 컬럼 설정

[컬럼 설정] 패널에서 표시할 컬럼 선택 및 순서 조정:

순서 컬럼 표시명 너비 정렬 표시 특수 설정
1 status 상태 80 중앙 뱃지 스타일 (색상별)
2 item_number 품번코드 140 좌측
3 item_name 품명 200 좌측 굵게 표시
4 size 규격 150 좌측
5 material 재질 150 좌측
6 inventory_unit 재고단위 100 중앙
7 weight 중량 80 우측
8 unit 단위 80 중앙
9 image 이미지 80 중앙 이미지 미리보기
10 division 구분 100 중앙 카테고리 표시
11 type 유형 100 중앙
12 selling_price 판매가 100 우측 숫자 포맷
13 standard_price 기준가 100 우측 숫자 포맷
14 meno 메모 180 좌측
15 writer 등록자 100 좌측 읽기 전용
16 created_date 등록일 120 중앙 읽기 전용
17 updated_date 수정일 120 중앙 읽기 전용

Step 4: 기능 설정

설정 항목 설정 값 설명
체크박스 사용 다중 선택 활성화
페이지네이션 사용
페이지 크기 20 기본 표시 행 수
정렬 사용 컬럼 헤더 클릭 정렬
컬럼 리사이즈 사용 컬럼 너비 조정
그룹핑 사용 Group By 기능

Step 5: 그룹핑 옵션 설정

Group By 드롭다운에 표시할 컬럼 선택:

  • status (상태)
  • division (구분)
  • type (유형)
  • inventory_unit (재고단위)
  • writer (등록자)

3.3 버튼 설정

좌측 버튼 그룹

코드변경 버튼
설정 항목 설정 값
라벨 코드변경
액션 타입 code_merge
스타일 secondary
선택 필수 체크 (복수 선택)
병합 대상 컬럼 item_number
데이터플로우 연결 품번코드 통합 (flow_id: 18)
업로드 버튼
설정 항목 설정 값
라벨 업로드
액션 타입 excel_upload
스타일 secondary
대상 테이블 item_info
다운로드 버튼
설정 항목 설정 값
라벨 다운로드
액션 타입 excel_download
스타일 secondary
대상 현재 테이블 리스트

우측 버튼 그룹

등록 버튼
설정 항목 설정 값
라벨 등록
액션 타입 modal
스타일 default
연결 화면 품목 등록/수정 화면 (아래 3.4 참조)
모달 제목 품목 등록
모달 사이즈 md
복사 버튼
설정 항목 설정 값
라벨 복사
액션 타입 copy
스타일 default
선택 필수 체크 (1개만)
연결 화면 품목 등록/수정 화면 (아래 3.4 참조)
동작 선택된 데이터를 복사하여 신규 등록 폼에 채움
수정 버튼
설정 항목 설정 값
라벨 수정
액션 타입 edit
스타일 default
선택 필수 체크 (1개만)
연결 화면 품목 등록/수정 화면 (아래 3.4 참조)
동작 선택된 데이터 수정 모드로 폼 열기
삭제 버튼
설정 항목 설정 값
라벨 삭제
액션 타입 delete
스타일 default
선택 필수 체크 (복수 선택 가능)
확인 메시지 선택한 품목을 삭제하시겠습니까?
삭제 후 동작 테이블 새로고침

3.4 품목 등록/수정 화면 (모달용 화면)

📌 별도 화면 생성 필요: 등록/복사/수정 버튼에 연결할 모달 화면을 새로 생성합니다.

💡 동일 화면 공유: 등록, 복사, 수정 버튼 모두 동일한 폼 화면을 사용합니다.

  • 등록: 빈 폼으로 열림
  • 복사: 선택된 데이터가 채워진 상태로 열림 (신규 등록)
  • 수정: 선택된 데이터가 채워진 상태로 열림 (기존 데이터 업데이트)

Step 1: 새 화면 생성

  1. 화면 관리에서 [+ 새 화면] 클릭
  2. 화면 정보 입력:
    • 화면명: 품목 등록/수정
    • 테이블: item_info
    • 화면 유형: 모달

Step 2: 폼 필드 배치

모달 레이아웃 배치도:

┌─────────────────────────────────────────────────────────────┐
│ 품목 등록/수정                                         [✕] │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────┐ ┌─────────────────────────┐   │
│  │ 품번코드 *              │ │ 품명 *                  │   │
│  │ [____________________]  │ │ [____________________]  │   │
│  └─────────────────────────┘ └─────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────┐ ┌─────────────────────────┐   │
│  │ 규격                    │ │ 재질                    │   │
│  │ [____________________]  │ │ [____________________]  │   │
│  └─────────────────────────┘ └─────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────┐ ┌───────────┐ ┌───────────┐   │
│  │ 재고단위 *              │ │ 중량      │ │ 중량단위  │   │
│  │ [EA           ▼]       │ │ [_______] │ │ [kg   ▼]  │   │
│  └─────────────────────────┘ └───────────┘ └───────────┘   │
│                                                             │
│  ┌─────────────────────────┐ ┌─────────────────────────┐   │
│  │ 구분 *                  │ │ 유형                    │   │
│  │ [원자재         ▼]     │ │ [반도체용       ▼]     │   │
│  └─────────────────────────┘ └─────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────┐ ┌─────────────────────────┐   │
│  │ 판매가                  │ │ 기준가                  │   │
│  │ [____________________]  │ │ [____________________]  │   │
│  └─────────────────────────┘ └─────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 메모                                                │   │
│  │ [__________________________________________________]│   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────┐                               │
│  │ 상태 *                  │                               │
│  │ [정상           ▼]     │                               │
│  └─────────────────────────┘                               │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                              [취소]  [💾 저장]              │
└─────────────────────────────────────────────────────────────┘

필드 목록:

순서 필드 (컬럼명) 라벨 입력 타입 필수 비고
1 item_number 품번코드 text
2 item_name 품명 text
3 size 규격 text
4 material 재질 text
5 inventory_unit 재고단위 select 옵션: EA, kg, L, Sheet, Box
6 weight 중량 number
7 unit 중량단위 select 옵션: g, kg, kg/L, t
8 division 구분 category 품목 구분 카테고리
9 type 유형 select 옵션: 반도체용, 태양광용, 산업용, 의료용, 건축용, 사출용, 화장품용
10 selling_price 판매가 number
11 standard_price 기준가 number
12 meno 메모 text
13 status 상태 select 옵션: 정상, 품절, 대기, 단종

Step 3: 버튼 배치

버튼 액션 타입 스타일 설정
저장 저장 primary 저장 후 모달 닫기, 부모 화면 테이블 새로고침
취소 모달 닫기 secondary

Step 4: 버튼에 화면 연결

  1. 메인 화면(품목정보)으로 돌아가기
  2. 등록 버튼 선택 → 설정 패널에서:
    • 액션 타입: modal
    • 연결 화면: 품목 등록/수정 선택
    • 모달 제목: 품목 등록
  3. 복사 버튼 선택 → 설정 패널에서:
    • 액션 타입: copy
    • 연결 화면: 품목 등록/수정 선택
    • 선택 필수: 체크
    • 동작: 선택된 데이터를 복사하여 폼에 채움 (신규 등록)
  4. 수정 버튼 선택 → 설정 패널에서:
    • 액션 타입: edit
    • 연결 화면: 품목 등록/수정 선택
    • 선택 필수: 체크
    • 동작: 선택된 데이터를 수정 모드로 폼에 채움

💡 참고: 컬럼별 스타일(뱃지 색상, 카테고리 표시 등)은 컴포넌트 기본 스타일을 따릅니다. 필요시 테이블 관리에서 컬럼별 상세 설정을 조정할 수 있습니다.


4. 컴포넌트 연동 설정

4.1 이벤트 흐름

[검색 입력]
    │
    ▼
v2-table-search-widget
    │ onFilterChange
    ▼
v2-table-list (자동 재조회)
    │
    ▼
[데이터 표시]


[등록/복사/수정 버튼 클릭]
    │
    ▼
[모달 열기] → [폼 입력] → [저장]
    │                        │
    │                        ▼
    │                   refreshTable 이벤트
    │                        │
    └────────────────────────┘
                             │
                             ▼
                    v2-table-list (재조회)

4.2 연동 설정

소스 컴포넌트 이벤트/액션 대상 컴포넌트 동작
검색 위젯 onFilterChange 테이블 리스트 필터 적용, 재조회
등록 버튼 click 모달 빈 폼으로 모달 열기
복사 버튼 click 모달 선택 데이터가 채워진 폼 열기 (신규)
수정 버튼 click 모달 선택 데이터가 채워진 폼 열기 (수정)
삭제 버튼 click 테이블 리스트 선택 항목 삭제
모달 저장 afterSave 테이블 리스트 refreshTable

4.3 TableOptionsContext 연동

v2-table-search-widget ──── TableOptionsContext ──── v2-table-list
        │                           │                      │
        │ registeredTables에서      │                      │
        │ item-table 참조           │                      │
        │                           │                      │
        └── onFilterChange() ───────┼──────────────────────┘
                                    │
                                    ▼
                            필터 조건 전달 & 재조회

5. 사용자 사용 예시 시나리오

시나리오 1: 품목 조회

단계 사용자 동작 기대 결과
1 화면 진입 전체 품목 목록 표시
2 상태 필터를 "정상"으로 선택 자동 필터링
3 품명에 "폴리머" 입력 후 검색 품명에 "폴리머" 포함된 품목 표시
4 Group by에서 "구분" 선택 division별 그룹핑

시나리오 2: 품목 등록

단계 사용자 동작 기대 결과
1 [등록] 버튼 클릭 빈 폼 모달 표시
2 데이터 입력 (품번코드, 품명, 규격 등) 입력 필드 채움
3 [저장] 버튼 클릭 저장 완료, 모달 닫힘, 목록 갱신

시나리오 3: 품목 복사

단계 사용자 동작 기대 결과
1 테이블에서 복사할 행 체크박스 선택 행 선택 표시
2 [복사] 버튼 클릭 선택된 데이터가 채워진 폼 모달 표시
3 필요시 데이터 수정 (품번코드 등) 필드 값 변경
4 [저장] 버튼 클릭 신규 등록 완료, 목록 갱신

시나리오 4: 품목 수정

단계 사용자 동작 기대 결과
1 테이블에서 행 체크박스 선택 행 선택 표시
2 [수정] 버튼 클릭 수정 모달 표시 (기존 데이터 로드)
3 데이터 수정 필드 값 변경
4 [저장] 버튼 클릭 저장 완료, 목록 갱신

시나리오 5: 품목 삭제

단계 사용자 동작 기대 결과
1 삭제할 행 체크박스 선택 (다중 가능) 행 선택 표시
2 [삭제] 버튼 클릭 삭제 확인 다이얼로그 표시
3 확인 삭제 완료, 목록 갱신

6. 검증 체크리스트

기본 기능

  • 데이터 조회가 정상 동작하는가?
  • 검색 필터 (상태, 품번코드, 품명)가 정상 동작하는가?
  • 신규 등록이 정상 동작하는가?
  • 복사 기능이 정상 동작하는가?
  • 수정이 정상 동작하는가?
  • 삭제가 정상 동작하는가?
  • 코드변경이 정상 동작하는가?
  • 엑셀 업로드가 정상 동작하는가?
  • 엑셀 다운로드가 정상 동작하는가?

테이블 기능

  • 페이지네이션이 정상 동작하는가?
  • 정렬이 정상 동작하는가?
  • 컬럼 너비 조정이 정상 동작하는가?
  • 체크박스 선택이 정상 동작하는가?

검색 위젯 연동

  • v2-table-search-widget과 v2-table-list 연동이 정상 동작하는가?
  • 필터 변경 시 자동 재조회가 동작하는가?
  • 초기화 버튼이 정상 동작하는가?

그룹핑 기능

  • Group by 선택 시 그룹핑이 정상 동작하는가?
  • 다중 그룹핑이 정상 동작하는가?

7. 참고 사항

관련 테이블

  • customer_item_mapping - 거래처별 품목 매핑
  • supplier_item_mapping - 공급업체별 품목 매핑
  • item_inspection_info - 품목 검사 정보
  • item_routing_version - 품목별 공정 버전
  • item_routing_detail - 품목별 공정 상세

특이 사항

  • division 컬럼은 카테고리 코드 (예: CATEGORY_191259)로 저장됨
  • meno 컬럼은 오타로 보임 (원래 memo)
  • selling_price, standard_price는 varchar로 저장됨 (숫자 형식 문자열)
  • company_code는 멀티테넌시용 회사 코드

8. DB INSERT용 JSON 설정 (screen_layouts_v2 방식)

📌 실제 화면 저장은 screen_definitions + screen_layouts_v2 테이블을 사용합니다. screen_layouts_v2는 전체 레이아웃을 하나의 JSON (layout_data)으로 저장합니다.

⚠️ 주의: 아래 JSON은 "품목정보" 화면 전용 예시입니다!

다른 화면 구현 시:

  1. 테이블 분석 필수 - 해당 화면이 사용하는 테이블 구조 파악
  2. 컬럼 재정의 - columns, fieldName 등을 해당 테이블에 맞게 작성
  3. 기능 요구사항 반영 - 버튼, 모달, 액션 등을 화면 요구사항에 맞게 구성
  4. ID 값 동적 처리 - screen_id, targetScreenId는 생성 시 할당되는 값 사용

8.1 테이블 구조

screen_definitions

컬럼명 타입 필수 기본값 설명
screen_id integer PK 자동 생성 (시퀀스) 화면 고유 ID
screen_name varchar(100) - 화면명
screen_code varchar(50) 자동 생성 {company_code}_{순번} 형식
table_name varchar(100) - 기본 테이블명
company_code varchar(50) - 회사 코드
description text - 화면 설명
is_active char(1) 'Y' Y=활성, N=비활성, D=삭제
created_date timestamp CURRENT_TIMESTAMP 생성일시
db_source_type varchar(10) 'internal' internal/external
data_source_type varchar(20) 'database' database/rest_api

screen_layouts_v2

컬럼명 타입 필수 기본값 설명
layout_id integer PK 자동 생성 (시퀀스) 레이아웃 고유 ID
screen_id integer - 화면 ID (FK)
company_code varchar(20) - 회사 코드
layout_data jsonb '{}' 전체 레이아웃 JSON
created_at timestamp now() 생성일시
updated_at timestamp now() 수정일시

8.2 화면 정의 (screen_definitions)

⚠️ screen_code는 API 호출 시 자동 생성됩니다. ({company_code}_{순번} 형식)

필수 입력 필드:

{
  "screenName": "품목정보",
  "tableName": "item_info",
  "companyCode": "COMPANY_7",
  "description": "품목 기본정보 관리 화면"
}

전체 필드 (자동 생성 포함):

{
  "screen_id": 140,
  "screen_name": "품목정보",
  "screen_code": "COMPANY_7_3",
  "table_name": "item_info",
  "company_code": "COMPANY_7",
  "description": "품목 기본정보 관리 화면",
  "is_active": "Y",
  "db_source_type": "internal",
  "data_source_type": "database",
  "created_date": "2025-01-29T00:00:00.000Z"
}

8.2 레이아웃 데이터 (screen_layouts_v2.layout_data)

전체 레이아웃을 하나의 JSON으로 저장

{
  "version": "2.0",
  "components": [
    {
      "id": "comp_search",
      "url": "@/lib/registry/components/v2-table-search-widget",
      "size": { "width": 1920, "height": 80 },
      "position": { "x": 0, "y": 20, "z": 1 },
      "overrides": {
        "type": "v2-table-search-widget",
        "label": "검색 필터",
        "webTypeConfig": {}
      },
      "displayOrder": 0
    },
    {
      "id": "comp_table",
      "url": "@/lib/registry/components/v2-table-list",
      "size": { "width": 1920, "height": 930 },
      "position": { "x": 0, "y": 150, "z": 1 },
      "overrides": {
        "type": "v2-table-list",
        "label": "테이블 리스트",
        "filter": { "enabled": true, "filters": [] },
        "height": "auto",
        "actions": { "actions": [], "bulkActions": false, "showActions": false },
        "columns": [
          { "align": "left", "order": 0, "format": "text", "visible": true, "sortable": true, "columnName": "status", "searchable": true, "displayName": "status" },
          { "align": "left", "order": 1, "format": "text", "visible": true, "sortable": true, "columnName": "item_number", "searchable": true, "displayName": "item_number" },
          { "align": "left", "order": 2, "format": "text", "visible": true, "sortable": true, "columnName": "item_name", "searchable": true, "displayName": "item_name" },
          { "align": "left", "order": 3, "format": "text", "visible": true, "sortable": true, "columnName": "size", "searchable": true, "displayName": "size" },
          { "align": "left", "order": 4, "format": "text", "visible": true, "sortable": true, "columnName": "material", "searchable": true, "displayName": "material" },
          { "align": "left", "order": 5, "format": "text", "visible": true, "sortable": true, "columnName": "inventory_unit", "searchable": true, "displayName": "inventory_unit" },
          { "align": "left", "order": 6, "format": "text", "visible": true, "sortable": true, "columnName": "weight", "searchable": true, "displayName": "weight" },
          { "align": "left", "order": 7, "format": "text", "visible": true, "sortable": true, "columnName": "unit", "searchable": true, "displayName": "unit" },
          { "align": "left", "order": 8, "format": "text", "visible": true, "sortable": true, "columnName": "division", "searchable": true, "displayName": "division" },
          { "align": "left", "order": 9, "format": "text", "visible": true, "sortable": true, "columnName": "type", "searchable": true, "displayName": "type" },
          { "align": "left", "order": 10, "format": "text", "visible": true, "sortable": true, "columnName": "writer", "searchable": true, "displayName": "writer" }
        ],
        "autoLoad": true,
        "checkbox": { "enabled": true, "multiple": true, "position": "left", "selectAll": true },
        "pagination": { "enabled": true, "pageSize": 20, "showPageInfo": true, "pageSizeOptions": [10, 20, 50, 100], "showSizeSelector": true },
        "showFooter": true,
        "showHeader": true,
        "tableStyle": { "theme": "default", "rowHeight": "normal", "borderStyle": "light", "headerStyle": "default", "hoverEffect": true, "alternateRows": true },
        "displayMode": "table",
        "stickyHeader": false,
        "selectedTable": "item_info",
        "webTypeConfig": {},
        "horizontalScroll": { "enabled": true, "maxColumnWidth": 300, "minColumnWidth": 100, "maxVisibleColumns": 8 }
      },
      "displayOrder": 1
    },
    {
      "id": "comp_btn_code_merge",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 88, "height": 40 },
      "position": { "x": 10, "y": 100, "z": 1 },
      "overrides": {
        "text": "코드변경",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "code_merge",
          "errorMessage": "저장 중 오류가 발생했습니다.",
          "successMessage": "저장되었습니다.",
          "mergeColumnName": "item_number"
        },
        "variant": "primary",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 2
    },
    {
      "id": "comp_btn_upload",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 88, "height": 40 },
      "position": { "x": 110, "y": 100, "z": 1 },
      "overrides": {
        "text": "업로드",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "excel_upload",
          "errorMessage": "업로드 중 오류가 발생했습니다.",
          "successMessage": "업로드되었습니다."
        },
        "variant": "primary",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 3
    },
    {
      "id": "comp_btn_download",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 88, "height": 40 },
      "position": { "x": 210, "y": 100, "z": 1 },
      "overrides": {
        "text": "다운로드",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "excel_download",
          "errorMessage": "다운로드 중 오류가 발생했습니다.",
          "successMessage": "다운로드되었습니다."
        },
        "variant": "primary",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 4
    },
    {
      "id": "comp_btn_register",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 80, "height": 40 },
      "position": { "x": 1550, "y": 100, "z": 1 },
      "overrides": {
        "text": "등록",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "modal",
          "modalSize": "lg",
          "modalTitle": "품목 등록",
          "targetScreenId": {{modal_screen_id}},
          "errorMessage": "저장 중 오류가 발생했습니다.",
          "successMessage": "저장되었습니다."
        },
        "variant": "primary",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 5
    },
    {
      "id": "comp_btn_copy",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 80, "height": 40 },
      "position": { "x": 1640, "y": 100, "z": 1 },
      "overrides": {
        "text": "복사",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "copy",
          "modalSize": "lg",
          "modalTitle": "품목 복사",
          "targetScreenId": {{modal_screen_id}},
          "errorMessage": "복사 중 오류가 발생했습니다.",
          "successMessage": "복사되었습니다."
        },
        "variant": "primary",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 6
    },
    {
      "id": "comp_btn_edit",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 80, "height": 40 },
      "position": { "x": 1730, "y": 100, "z": 1 },
      "overrides": {
        "text": "수정",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "edit",
          "modalSize": "lg",
          "modalTitle": "품목 수정",
          "targetScreenId": {{modal_screen_id}},
          "errorMessage": "수정 중 오류가 발생했습니다.",
          "successMessage": "수정되었습니다."
        },
        "variant": "primary",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 7
    },
    {
      "id": "comp_btn_delete",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 80, "height": 40 },
      "position": { "x": 1820, "y": 100, "z": 1 },
      "overrides": {
        "text": "삭제",
        "type": "v2-button-primary",
        "label": "기본 버튼",
        "action": {
          "type": "delete",
          "errorMessage": "삭제 중 오류가 발생했습니다.",
          "successMessage": "삭제되었습니다."
        },
        "variant": "danger",
        "actionType": "button",
        "webTypeConfig": { "variant": "default", "actionType": "custom" }
      },
      "displayOrder": 8
    }
  ]
}

8.3 모달 화면 (품목 등록/수정)

화면 정의 (필수 입력)

{
  "screenName": "품목 등록/수정",
  "tableName": "item_info",
  "companyCode": "COMPANY_7",
  "description": "품목 등록/수정 폼 화면"
}

레이아웃 데이터 (screen_layouts_v2.layout_data)

📌 실제 적용된 레이아웃 - 품목 등록/수정 폼 (모달용)

{
  "version": "2.0",
  "components": [
    {
      "id": "comp_item_number",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 20, "y": 20, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "품번코드",
        "fieldName": "item_number",
        "placeholder": "품번코드를 입력하세요",
        "required": true
      },
      "displayOrder": 0
    },
    {
      "id": "comp_item_name",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 340, "y": 20, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "품명",
        "fieldName": "item_name",
        "placeholder": "품명을 입력하세요",
        "required": true
      },
      "displayOrder": 1
    },
    {
      "id": "comp_size",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 20, "y": 100, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "규격",
        "fieldName": "size",
        "placeholder": "규격을 입력하세요"
      },
      "displayOrder": 2
    },
    {
      "id": "comp_material",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 340, "y": 100, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "재질",
        "fieldName": "material",
        "placeholder": "재질을 입력하세요"
      },
      "displayOrder": 3
    },
    {
      "id": "comp_inventory_unit",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 20, "y": 180, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "재고단위",
        "fieldName": "inventory_unit",
        "placeholder": "재고단위를 입력하세요"
      },
      "displayOrder": 4
    },
    {
      "id": "comp_weight",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 340, "y": 180, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "중량",
        "fieldName": "weight",
        "placeholder": "중량을 입력하세요"
      },
      "displayOrder": 5
    },
    {
      "id": "comp_unit",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 20, "y": 260, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "단위",
        "fieldName": "unit",
        "placeholder": "단위를 입력하세요"
      },
      "displayOrder": 6
    },
    {
      "id": "comp_division",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 340, "y": 260, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "구분",
        "fieldName": "division",
        "placeholder": "구분을 입력하세요"
      },
      "displayOrder": 7
    },
    {
      "id": "comp_type",
      "url": "@/lib/registry/components/v2-input",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 20, "y": 340, "z": 1 },
      "overrides": {
        "type": "v2-input",
        "label": "유형",
        "fieldName": "type",
        "placeholder": "유형을 입력하세요"
      },
      "displayOrder": 8
    },
    {
      "id": "comp_status",
      "url": "@/lib/registry/components/v2-select",
      "size": { "width": 300, "height": 60 },
      "position": { "x": 340, "y": 340, "z": 1 },
      "overrides": {
        "type": "v2-select",
        "label": "상태",
        "fieldName": "status",
        "config": {
          "mode": "dropdown",
          "source": "static",
          "options": [
            { "value": "정상", "label": "정상" },
            { "value": "품절", "label": "품절" },
            { "value": "대기", "label": "대기" },
            { "value": "단종", "label": "단종" }
          ]
        }
      },
      "displayOrder": 9
    },
    {
      "id": "comp_btn_save",
      "url": "@/lib/registry/components/v2-button-primary",
      "size": { "width": 100, "height": 40 },
      "position": { "x": 540, "y": 420, "z": 1 },
      "overrides": {
        "text": "저장",
        "type": "v2-button-primary",
        "label": "저장 버튼",
        "action": {
          "type": "save",
          "closeModalAfterSave": true,
          "refreshParentTable": true,
          "successMessage": "저장되었습니다.",
          "errorMessage": "저장 중 오류가 발생했습니다."
        },
        "variant": "primary",
        "actionType": "button"
      },
      "displayOrder": 10
    }
  ]
}

8.4 API 호출 방식

📌 실제 화면 생성은 API를 통해 진행됩니다. screen_code는 서버에서 자동 생성됩니다.

Step 1: 화면 코드 자동 생성 API

GET /api/screens/generate-code?companyCode=COMPANY_7

응답:

{
  "success": true,
  "data": { "screenCode": "COMPANY_7_4" }
}

Step 2: 화면 생성 API

POST /api/screens
Content-Type: application/json
Authorization: Bearer {{token}}

{
  "screenName": "품목정보",
  "screenCode": "COMPANY_7_4",
  "tableName": "item_info",
  "companyCode": "COMPANY_7",
  "description": "품목 기본정보 관리 화면"
}

응답:

{
  "success": true,
  "data": {
    "screenId": 141,
    "screenCode": "COMPANY_7_4",
    "screenName": "품목정보"
  }
}

Step 3: 레이아웃 저장 API

PUT /api/screens/141/layout-v2
Content-Type: application/json
Authorization: Bearer {{token}}

{
  "layoutData": {
    "version": "2.0",
    "components": [ /* 8.2의 components 배열 */ ]
  }
}

8.5 SQL 직접 INSERT (실제 적용 방식)

📌 실제 테스트 완료된 방식입니다. Docker psql을 통해 직접 DB에 삽입합니다.

Step 1: 모달 화면 먼저 생성 (등록/수정 폼)

-- 모달 화면 정의
INSERT INTO screen_definitions (
  screen_code, screen_name, table_name, company_code, description, is_active
) VALUES (
  'COMPANY_19_ITEM_MODAL', 'Item Register/Edit Modal', 'item_info', 'COMPANY_19', 
  'Item registration and edit form modal', 'Y'
) RETURNING screen_id;
-- 예: screen_id = 3731 반환됨

Step 2: 모달 레이아웃 저장

-- 레이아웃 JSON을 파일로 저장 후 INSERT (한글 인코딩 문제 방지)
INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data, created_at, updated_at)
VALUES (
  3731,  -- Step 1에서 반환된 screen_id
  'COMPANY_19',
  '{"version":"2.0","components":[/* 8.3의 모달 components 배열 */]}'::jsonb,
  NOW(), NOW()
);

Step 3: 메인 화면 생성

-- 메인 화면 정의
INSERT INTO screen_definitions (
  screen_code, screen_name, table_name, company_code, description, is_active
) VALUES (
  'COMPANY_19_ITEM_INFO', 'Item Info', 'item_info', 'COMPANY_19', 
  'Item master data management', 'Y'
) RETURNING screen_id;
-- 예: screen_id = 3730 반환됨

Step 4: 메인 레이아웃 저장 (모달 연결)

-- targetScreenId를 Step 1에서 생성한 모달 screen_id로 치환
INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data, created_at, updated_at)
VALUES (
  3730,  -- Step 3에서 반환된 screen_id
  'COMPANY_19',
  '{"version":"2.0","components":[/* 8.2의 components 배열, targetScreenId: 3731 */]}'::jsonb,
  NOW(), NOW()
);

Step 5: 메뉴에 화면 연결 (선택사항)

-- 기존 메뉴에 화면 연결 (screen_menu_assignments 테이블 사용)
INSERT INTO screen_menu_assignments (screen_id, menu_id, company_code, display_order)
VALUES (3730, 55566, 'COMPANY_19', 1);

8.6 화면 생성 순서 (중요!)

1. 모달 화면 생성 (screen_definitions INSERT)
      │
      ▼
2. 모달 레이아웃 저장 (screen_layouts_v2 INSERT)
      │
      ▼
3. 메인 화면 생성 (screen_definitions INSERT)
      │
      ▼
4. 메인 레이아웃 저장 (screen_layouts_v2 INSERT)
   └── targetScreenId에 모달 screen_id 사용!
      │
      ▼
5. (선택) 메뉴에 화면 연결

8.7 주의사항

항목 설명
screen_code 회사별 고유, 형식: {COMPANY_CODE}_{용도} (예: COMPANY_19_ITEM_INFO)
screen_id AUTO INCREMENT, INSERT 후 RETURNING으로 획득
component.id comp_ prefix 권장, 화면 내 중복 불가
component.url @/lib/registry/components/v2-xxx 형식 정확히 사용
component.type overrides.type과 URL 마지막 부분 일치 필요
targetScreenId 숫자 (문자열 아님), 모달 화면 먼저 생성 필요
version 반드시 "2.0" 사용
layout_data JSONB 타입, 복잡한 JSON은 파일로 저장 후 -f 옵션으로 실행
UNIQUE 제약 (screen_id, company_code) 조합이 유니크
한글 처리 Docker psql에서 한글 직접 입력 시 인코딩 문제 → 영문 사용 또는 파일 사용

8.8 컴포넌트 타입 레퍼런스

컴포넌트 URL type (overrides)
텍스트 입력 v2-input v2-input
선택 (드롭다운) v2-select v2-select
날짜 입력 v2-date v2-date
버튼 v2-button-primary v2-button-primary
테이블 리스트 v2-table-list v2-table-list
검색 위젯 v2-table-search-widget v2-table-search-widget
텍스트 표시 v2-text-display v2-text-display
구분선 v2-divider-line v2-divider-line
섹션 카드 v2-section-card v2-section-card

9. 화면 구현 체크리스트

📋 새로운 화면을 구현할 때 아래 체크리스트를 순서대로 확인하세요.

9.1 분석 단계

체크 항목 설명
테이블 구조 분석 해당 화면이 사용할 테이블 스키마 확인 (컬럼명, 타입, 필수 여부)
화면 기능 파악 조회/등록/수정/삭제, 검색, 필터, 그룹핑 등 필요 기능 목록화
컴포넌트 매핑 필요 기능 → V2 컴포넌트 매핑 (2.2 목록 참조)
구현 불가 항목 확인 현재 V2 컴포넌트로 구현 불가능한 기능 파악
대체 방안 검토 구현 불가 항목에 대해 기존 컴포넌트 조합으로 대체 가능 여부 확인

9.2 문서 작성 단계

체크 항목 설명
테이블 정의 작성 1.1~1.2 형식으로 테이블/컬럼 정보 작성
레이아웃 배치도 작성 2.1 형식으로 ASCII 다이어그램 작성
사용 컴포넌트 목록 2.3 형식으로 이 화면에서 사용할 컴포넌트 정리
모달 화면 정의 등록/수정 모달이 필요하면 별도 레이아웃 작성
JSON 설정 작성 8.2~8.3 형식으로 layout_data JSON 작성
구현 불가/대체 방안 명시 해당 사항 있으면 문서에 섹션 추가

9.3 INSERT 전 확인

체크 항목 설명
company_code 확인 대상 회사 코드 정확한지 확인
screen_code 형식 {COMPANY_CODE}_{용도} 형식 준수
모달 먼저 생성 모달 화면이 있으면 반드시 먼저 INSERT
JSON 파일 준비 복잡한 JSON은 파일로 저장 (한글 인코딩 대비)
컴포넌트 ID 고유성 comp_ prefix, 화면 내 중복 없는지 확인
컴포넌트 URL/type 일치 url의 마지막 부분과 overrides.type 동일한지 확인
targetScreenId 치환 모달 screen_id를 숫자로 정확히 입력

9.4 INSERT 후 검증

체크 항목 설명
화면 접속 테스트 /screens/{screen_id} URL로 접속
컴포넌트 렌더링 확인 모든 컴포넌트가 "미구현" 없이 표시되는지 확인
검색 기능 테스트 검색 위젯 동작 확인
테이블 데이터 로드 테이블에 데이터 표시되는지 확인
버튼 동작 테스트 등록/수정/삭제 버튼 클릭 시 모달/액션 동작 확인
모달 폼 테스트 모달 열림, 입력 필드 표시, 저장 동작 확인
메뉴 연결 확인 (연결한 경우) 메뉴에서 화면 접근 가능한지 확인

9.5 문제 발생 시 확인 사항

증상 확인 사항
화면이 안 보임 screen_layouts_v2에 데이터 있는지 확인, company_code 일치 여부
"미구현 컴포넌트" 표시 urloverrides.type 일치 여부, 컴포넌트명 오타 확인
모달이 안 열림 targetScreenId가 숫자인지, 해당 screen_id 존재하는지 확인
테이블 데이터 없음 selectedTable 값 확인, 테이블에 데이터 존재 여부
버튼 동작 안 함 action.type 값 확인, actionType: "button" 설정 여부