V2 이벤트 시스템 통합 및 데이터 전달 인터페이스 구현: UnifiedRepeater 컴포넌트에 데이터 제공 및 수신 인터페이스를 추가하여 다른 컴포넌트와의 데이터 연동을 개선하였습니다. 또한, AggregationWidgetComponent와 RepeatContainerComponent에서 V2 표준 이벤트를 구독하여 데이터 변경 이벤트를 효율적으로 처리하도록 수정하였습니다. 이를 통해 컴포넌트 간의 데이터 흐름과 사용자 경험을 향상시켰습니다.

This commit is contained in:
juseok2
2026-01-29 23:20:23 +09:00
parent 8cdb8a3047
commit 3803b7dce1
16 changed files with 4654 additions and 85 deletions

View File

@@ -0,0 +1,974 @@
# 품목정보 (Item Info)
> Screen ID: /screens/140
> 메뉴 경로: 기준정보 > 품목정보
> 테이블: `item_info`
## 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-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 | 삭제 |
---
## 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`)으로 저장합니다.
### 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}_{순번}` 형식)
**필수 입력 필드:**
```json
{
"screenName": "품목정보",
"tableName": "item_info",
"companyCode": "COMPANY_7",
"description": "품목 기본정보 관리 화면"
}
```
**전체 필드 (자동 생성 포함):**
```json
{
"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으로 저장
```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": 0
},
{
"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",
"dataflowConfig": {
"flowConfig": { "flowId": 18, "flowName": "품번코드 통합", "contextData": {}, "executionTiming": "after" },
"selectedDiagramId": 18
}
}
},
"displayOrder": 0
},
{
"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": 0
},
{
"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": 0
},
{
"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": "md",
"modalTitle": "품목 등록",
"errorMessage": "저장 중 오류가 발생했습니다.",
"successMessage": "저장되었습니다.",
"targetScreenId": "{{modal_screen_id}}"
},
"variant": "primary",
"actionType": "button",
"webTypeConfig": { "variant": "default", "actionType": "custom" }
},
"displayOrder": 0
},
{
"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",
"errorMessage": "저장 중 오류가 발생했습니다.",
"successMessage": "저장되었습니다.",
"targetScreenId": "{{modal_screen_id}}"
},
"variant": "primary",
"actionType": "button",
"webTypeConfig": { "variant": "default", "actionType": "custom" }
},
"displayOrder": 0
},
{
"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",
"errorMessage": "저장 중 오류가 발생했습니다.",
"successMessage": "저장되었습니다.",
"targetScreenId": "{{modal_screen_id}}"
},
"variant": "primary",
"actionType": "button",
"webTypeConfig": { "variant": "default", "actionType": "custom" }
},
"displayOrder": 0
},
{
"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": "primary",
"actionType": "button",
"webTypeConfig": { "variant": "default", "actionType": "custom" }
},
"displayOrder": 0
}
]
}
```
### 8.3 모달 화면 (품목 등록/수정)
#### 화면 정의 (필수 입력)
```json
{
"screenName": "품목 등록/수정",
"tableName": "item_info",
"companyCode": "COMPANY_7",
"description": "품목 등록/수정 폼 화면"
}
```
#### 레이아웃 데이터 (screen_layouts_v2.layout_data)
```json
{
"version": "2.0",
"components": [
{
"id": "comp_item_number",
"url": "@/lib/registry/components/v2-text-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 20, "z": 1 },
"overrides": {
"type": "v2-text-input",
"label": "품번코드",
"fieldName": "item_number",
"placeholder": "품번코드를 입력하세요",
"required": true
},
"displayOrder": 0
},
{
"id": "comp_item_name",
"url": "@/lib/registry/components/v2-text-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 20, "z": 1 },
"overrides": {
"type": "v2-text-input",
"label": "품명",
"fieldName": "item_name",
"placeholder": "품명을 입력하세요",
"required": true
},
"displayOrder": 1
},
{
"id": "comp_status",
"url": "@/lib/registry/components/v2-select-basic",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 100, "z": 1 },
"overrides": {
"type": "v2-select-basic",
"label": "상태",
"fieldName": "status",
"options": ["정상", "품절", "대기", "단종"]
},
"displayOrder": 2
},
{
"id": "comp_btn_save",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 80, "height": 40 },
"position": { "x": 400, "y": 500, "z": 1 },
"overrides": {
"text": "저장",
"type": "v2-button-primary",
"label": "저장 버튼",
"action": {
"type": "save",
"closeModalAfterSave": true,
"refreshParentTable": true,
"successMessage": "저장되었습니다.",
"errorMessage": "저장 중 오류가 발생했습니다."
},
"variant": "primary",
"actionType": "button"
},
"displayOrder": 20
}
]
}
```
### 8.4 API 호출 방식
> 📌 실제 화면 생성은 API를 통해 진행됩니다. `screen_code`는 서버에서 자동 생성됩니다.
#### Step 1: 화면 코드 자동 생성 API
```http
GET /api/screens/generate-code?companyCode=COMPANY_7
```
**응답:**
```json
{
"success": true,
"data": { "screenCode": "COMPANY_7_4" }
}
```
#### Step 2: 화면 생성 API
```http
POST /api/screens
Content-Type: application/json
Authorization: Bearer {{token}}
{
"screenName": "",
"screenCode": "COMPANY_7_4",
"tableName": "item_info",
"companyCode": "COMPANY_7",
"description": " "
}
```
**응답:**
```json
{
"success": true,
"data": {
"screenId": 141,
"screenCode": "COMPANY_7_4",
"screenName": "품목정보"
}
}
```
#### Step 3: 레이아웃 저장 API
```http
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 (참고용)
> ⚠️ 일반적으로 API를 사용하지만, 대량 마이그레이션 시 직접 SQL 사용 가능
```sql
-- Step 1: 화면 정의 (screen_code는 수동 지정 필요)
INSERT INTO screen_definitions (
screen_name, screen_code, table_name, company_code, description
) VALUES (
'품목정보', 'COMPANY_7_4', 'item_info', 'COMPANY_7', '품목 기본정보 관리 화면'
) RETURNING screen_id;
-- Step 2: 레이아웃 저장 (screen_id 사용)
INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data)
VALUES (
141, -- 위에서 반환된 screen_id
'COMPANY_7',
'{"version": "2.0", "components": [...]}'::jsonb
);
```
### 8.6 주의사항
| 항목 | 설명 |
|------|------|
| `screen_code` | API 사용 시 `generateScreenCode` 먼저 호출, 형식: `{company_code}_{순번}` |
| `screen_id` | 화면 생성 후 반환되는 값, 레이아웃 저장 시 필요 |
| `component.id` | 고유 ID (UUID 또는 `comp_` prefix), 중복 불가 |
| `component.url` | `@/lib/registry/components/v2-xxx` 형식 |
| `{{modal_screen_id}}` | 모달 화면 먼저 생성 후 실제 ID로 치환 |
| `version` | 반드시 `"2.0"` 사용 |
| UNIQUE 제약 | `screen_layouts_v2``(screen_id, company_code)` 조합이 유니크 |