ERP-node/docs/screen-implementation-guide/02_sales/order.md

1277 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 수주관리 (Sales Order Management)
> Screen ID: /screens/156
> 메뉴 경로: 영업관리 > 수주관리
> 테이블: `sales_order_mng`
---
## ⚠️ 문서 사용 안내
> **이 문서는 "수주관리" 화면의 구현 예시입니다.**
>
> ### 📌 중요: JSON 데이터는 참고용입니다!
>
> 이 문서에 포함된 JSON 설정(레이아웃, 컴포넌트 구성 등)은 **수주관리 화면에 특화된 예시**입니다.
>
> **다른 화면을 구현할 때:**
> 1. 이 JSON을 그대로 복사해서 사용하지 마세요
> 2. 해당 화면의 **테이블 구조를 먼저 분석**하세요
> 3. 화면의 **요구사항과 기능을 파악**하세요
> 4. 분석 결과에 맞는 **새로운 JSON 구조를 작성**하세요
---
## 1. 테이블 선택 및 화면 구조
### 1.1 사용 테이블
| 테이블명 | 용도 | 비고 |
|----------|------|------|
| `sales_order_mng` | 수주 마스터 데이터 | 주 테이블 |
| `customer_mng` | 거래처 정보 | FK: partner_id |
| `item_info` | 품목 정보 | FK: part_code |
### 1.2 테이블 컬럼 정의 (실제 DB 기준)
| 컬럼명 | 표시명 | 타입 | 필수 | 설명 |
|--------|--------|------|------|------|
| `id` | ID | integer | PK | 자동 생성 (시퀀스) |
| `company_code` | 회사코드 | varchar | ✅ | 멀티테넌시 |
| `order_no` | 수주번호 | varchar | ✅ | 수주 고유 코드 |
| `order_date` | 수주일 | date | | 수주 등록일 |
| `due_date` | 납기일 | date | | 납품 예정일 |
| `partner_id` | 거래처ID | varchar | | 거래처 코드 (FK) |
| `delivery_partner_id` | 납품처ID | varchar | | 납품처 코드 |
| `delivery_address` | 납품장소 | text | | 납품 주소 |
| `shipping_method` | 배송방법 | varchar | | 택배, 화물, 직송 등 |
| `part_code` | 품목코드 | varchar | | 품목 코드 (FK) |
| `part_name` | 품명 | varchar | | 품목명 |
| `spec` | 규격 | varchar | | 규격 정보 |
| `material` | 재질 | varchar | | 재질 정보 |
| `order_qty` | 수주수량 | numeric | | 기본값 0 |
| `ship_qty` | 출하수량 | numeric | | 기본값 0 |
| `balance_qty` | 잔량 | numeric | | 기본값 0 (수주수량 - 출하수량) |
| `inventory_qty` | 현재고 | numeric | | 기본값 0 |
| `plan_ship_qty` | 출하계획량 | numeric | | 기본값 0 |
| `unit_price` | 단가 | numeric | | 기본값 0 |
| `total_amount` | 금액 | numeric | | 기본값 0 (수주수량 × 단가) |
| `status` | 상태 | varchar | | 수주, 진행중, 완료, 취소 (기본값: 수주) |
| `manager_id` | 담당자ID | varchar | | 담당자 ID |
| `manager_name` | 담당자명 | varchar | | 담당자 이름 |
| `memo` | 메모 | text | | 비고 |
| `sales_type` | 영업유형 | varchar | | 내수, 수출 등 |
| `part_name_eng` | 품명(영문) | varchar | | 영문 품목명 |
| `item_due_date` | 품목납기일 | varchar | | 품목별 납기일 |
| `incoterms` | 인코텀즈 | varchar | | 무역조건 (수출용) |
| `payment_term` | 결제조건 | varchar | | 결제 조건 |
| `port_of_loading` | 선적항 | varchar | | 선적 항구 (수출용) |
| `port_of_discharge` | 도착항 | varchar | | 도착 항구 (수출용) |
| `hs_code` | HS코드 | varchar | | 관세 코드 (수출용) |
| `currency` | 통화 | varchar | | 통화 코드 |
| `created_date` | 등록일 | timestamp | | 자동 생성 |
| `created_by` | 등록자 | varchar | | 등록자 ID |
| `updated_date` | 수정일 | timestamp | | 자동 갱신 |
| `updated_by` | 수정자 | varchar | | 수정자 ID |
| `writer` | 작성자 | varchar | | 작성자 ID |
### 1.3 화면 구조 개요
- **화면 유형**: 목록형 (단일 테이블 CRUD)
- **주요 기능**:
- 수주 조회/검색/필터링
- 수주 등록/수정/삭제
- 그룹핑 (Group By)
- 출하계획 연동
- 엑셀 업로드/다운로드
- 통계 표시 (총 금액, 총 수량)
---
## 2. 컴포넌트 배치도
### 2.1 전체 레이아웃
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ [검색 영역] │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-search-widget │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ 수주번호 │ │ 거래처 │ │ 품목명 │ │ 상태 │ │ 수주일 │ │ │
│ │ │ (text) │ │ (select) │ │ (text) │ │ (select) │ │ (date) │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌──────┐ │ │
│ │ │ 사용자 │ │ 엑셀 │ │엑셀 │ │ │
│ │ │ 옵션 │ │ 업로드 │ │다운 │ │ │
│ │ └─────────┘ └──────────┘ └──────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────┤
│ [테이블 헤더 + 액션 버튼 + 통계] │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 📋 수주 목록 (10) 총 금액: 1,234,000원 총 수량: 5,000개 [Group by ▼]│ │
│ │ [수주등록][수정][삭제][출하계획] │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────────┤
│ [데이터 테이블] │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ v2-table-list │ │
│ │ ┌──┬────────┬────────┬────────┬────────┬──────┬──────┬──────┬────────┐ │ │
│ │ │☐ │수주번호│거래처 │품목코드│품명 │규격 │재질 │단위 │수주수량│ │ │
│ │ ├──┼────────┼────────┼────────┼────────┼──────┼──────┼──────┼────────┤ │ │
│ │ │☐ │ORD-001 │삼성전자│ITEM001 │볼트 M8 │M8x20 │SUS304│EA │1,000 │ │ │
│ │ │☐ │ORD-002 │LG전자 │ITEM002 │너트 M8 │M8 │SUS304│EA │2,000 │ │ │
│ │ └──┴────────┴────────┴────────┴────────┴──────┴──────┴──────┴────────┘ │ │
│ │ (수평 스크롤: 출하수량, 잔량, 현재고, 출하계획량, 단가, 금액, 납품처, │ │
│ │ 납품장소, 배송방법, 납기일, 수주일, 상태, 담당자, 메모) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 2.2 사용 가능한 V2 컴포넌트 목록
> 📌 **V2 컴포넌트 전체 목록** - 화면 구성 시 사용 가능한 컴포넌트
| 컴포넌트 ID | 설명 | 카테고리 |
|-------------|------|----------|
| `v2-input` | 텍스트, 숫자, 비밀번호, 이메일 등 입력 | 입력 |
| `v2-select` | 드롭다운, 콤보박스, 라디오, 체크박스 | 입력 |
| `v2-date` | 날짜/시간 입력 | 입력 |
| `v2-button-primary` | 버튼 | 액션 |
| `v2-table-list` | 테이블 리스트 (CRUD) | 테이블 |
| `v2-table-search-widget` | 테이블 검색/필터 위젯 | 유틸리티 |
| `v2-aggregation-widget` | 집계 위젯 | 위젯 |
| `v2-text-display` | 텍스트 표시 (읽기 전용) | 표시 |
### 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-aggregation-widget` | 통계 표시 (총 금액, 총 수량) |
| `v2-input` | 모달 - 텍스트 입력 필드 |
| `v2-select` | 모달 - 선택 필드 |
| `v2-date` | 모달 - 날짜 입력 필드 |
---
## 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: 데이터 소스 설정
| 설정 항목 | 설정 값 |
|-----------|---------|
| 테이블 선택 | `sales_order_mng` |
| 자동 컬럼 생성 | ✅ 체크 (테이블 컬럼 자동 로드) |
#### Step 3: 컬럼 설정
**[컬럼 설정]** 패널에서 표시할 컬럼 선택 및 순서 조정:
| 순서 | 컬럼 | 표시명 | 너비 | 정렬 | 표시 | 특수 설정 |
|------|------|--------|------|------|------|-----------|
| 1 | `order_no` | 수주번호 | 130 | 좌측 | ✅ | 굵게 표시 |
| 2 | `partner_id` | 거래처 | 150 | 좌측 | ✅ | 조인: customer_mng.name |
| 3 | `part_code` | 품목코드 | 130 | 좌측 | ✅ | |
| 4 | `part_name` | 품명 | 180 | 좌측 | ✅ | |
| 5 | `spec` | 규격 | 120 | 좌측 | ✅ | |
| 6 | `material` | 재질 | 100 | 좌측 | ✅ | |
| 7 | `unit` | 단위 | 80 | 중앙 | ✅ | 기본값: EA |
| 8 | `order_qty` | 수주수량 | 100 | 우측 | ✅ | 숫자 포맷 |
| 9 | `ship_qty` | 출하수량 | 100 | 우측 | ✅ | 숫자 포맷 |
| 10 | `balance_qty` | 잔량 | 100 | 우측 | ✅ | 숫자 포맷, 굵게 |
| 11 | `inventory_qty` | 현재고 | 100 | 우측 | ✅ | 숫자 포맷 |
| 12 | `plan_ship_qty` | 출하계획량 | 100 | 우측 | ✅ | 숫자 포맷 |
| 13 | `unit_price` | 단가 | 120 | 우측 | ✅ | 숫자 포맷 |
| 14 | `total_amount` | 금액 | 140 | 우측 | ✅ | 숫자 포맷, 굵게 |
| 15 | `delivery_partner_id` | 납품처 | 150 | 좌측 | ✅ | |
| 16 | `delivery_address` | 납품장소 | 150 | 좌측 | ✅ | |
| 17 | `shipping_method` | 배송방법 | 120 | 중앙 | ✅ | |
| 18 | `due_date` | 납기일 | 120 | 중앙 | ✅ | 날짜 포맷 |
| 19 | `order_date` | 수주일 | 120 | 중앙 | ✅ | 날짜 포맷 |
| 20 | `status` | 상태 | 100 | 중앙 | ✅ | 뱃지 스타일 |
| 21 | `manager_name` | 담당자 | 100 | 좌측 | ✅ | |
| 22 | `memo` | 메모 | 200 | 좌측 | ✅ | |
#### Step 4: 기능 설정
| 설정 항목 | 설정 값 | 설명 |
|-----------|---------|------|
| 체크박스 | ✅ 사용 | 다중 선택 활성화 |
| 페이지네이션 | ✅ 사용 | |
| 페이지 크기 | 20 | 기본 표시 행 수 |
| 정렬 | ✅ 사용 | 컬럼 헤더 클릭 정렬 |
| 컬럼 리사이즈 | ✅ 사용 | 컬럼 너비 조정 |
| 그룹핑 | ✅ 사용 | Group By 기능 |
| 수평 스크롤 | ✅ 사용 | 컬럼 수가 많으므로 필수 |
#### Step 5: 그룹핑 옵션 설정
Group By 드롭다운에 표시할 컬럼 선택:
-`partner_id` (거래처)
-`status` (상태)
-`part_name` (품목명)
-`material` (재질)
---
### 3.3 버튼 설정
#### 검색 영역 우측 버튼
##### 사용자옵션 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `사용자옵션` |
| 아이콘 | ⚙️ |
| 액션 타입 | `custom` |
| 스타일 | `secondary` |
| 동작 | 사용자 옵션 모달 열기 |
##### 엑셀 업로드 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `엑셀 업로드` |
| 아이콘 | 📥 |
| 액션 타입 | `excel_upload` |
| 스타일 | `secondary` |
| 대상 테이블 | `sales_order_mng` |
##### 엑셀 다운로드 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `엑셀 다운로드` |
| 아이콘 | 📤 |
| 액션 타입 | `excel_download` |
| 스타일 | `secondary` |
| 대상 | 현재 테이블 리스트 |
#### 테이블 헤더 우측 버튼
##### 수주등록 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `수주 등록` |
| 아이콘 | |
| 액션 타입 | `modal` |
| 스타일 | `success` |
| 연결 화면 | 수주 등록/수정 화면 (아래 3.4 참조) |
| 모달 제목 | 수주 등록 |
| 모달 사이즈 | `lg` |
##### 수정 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `수정` |
| 아이콘 | ✏️ |
| 액션 타입 | `edit` |
| 스타일 | `secondary` |
| 선택 필수 | ✅ 체크 (1개만) |
| 연결 화면 | 수주 등록/수정 화면 (아래 3.4 참조) |
##### 삭제 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `삭제` |
| 아이콘 | 🗑️ |
| 액션 타입 | `delete` |
| 스타일 | `secondary` |
| 선택 필수 | ✅ 체크 (복수 선택 가능) |
| 확인 메시지 | 선택한 수주를 삭제하시겠습니까? |
##### 출하계획 버튼
| 설정 항목 | 설정 값 |
|-----------|---------|
| 라벨 | `출하계획` |
| 아이콘 | 🚚 |
| 액션 타입 | `custom` |
| 스타일 | `secondary` |
| 선택 필수 | ✅ 체크 (복수 선택 가능) |
| 동작 | 출하계획 슬라이드 패널 열기 |
---
### 3.4 수주 등록/수정 화면 (모달용 화면)
> 📌 **별도 화면 생성 필요**: 수주등록/수정 버튼에 연결할 모달 화면을 새로 생성합니다.
#### Step 1: 새 화면 생성
1. 화면 관리에서 **[+ 새 화면]** 클릭
2. 화면 정보 입력:
- 화면명: `수주 등록/수정`
- 테이블: `sales_order_mng`
- 화면 유형: `모달`
#### Step 2: 폼 필드 배치
**모달 레이아웃 배치도**:
```
┌─────────────────────────────────────────────────────────────────┐
│ 수주 등록/수정 [✕] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 수주번호 * │ │ 수주일 * │ │
│ │ [____________________] │ │ [____________________] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 거래처 * │ │ 품목코드 * │ │
│ │ [삼성전자 ▼] │ │ [____________________] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 품명 │ │ 규격 │ │
│ │ [____________________] │ │ [____________________] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 재질 │ │ 단위 │ │
│ │ [____________________] │ │ [EA ▼] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 수주수량 * │ │ 단가 * │ │
│ │ [____________________] │ │ [____________________] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 납기일 │ │ 상태 * │ │
│ │ [____________________] │ │ [수주 ▼] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 납품처 │ │ 납품장소 │ │
│ │ [____________________] │ │ [____________________] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 배송방법 │ │ 담당자 │ │
│ │ [택배 ▼] │ │ [____________________] │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 메모 │ │
│ │ [______________________________________________________]│ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [취소] [💾 저장] │
└─────────────────────────────────────────────────────────────────┘
```
**필드 목록**:
| 순서 | 필드 (컬럼명) | 라벨 | 입력 타입 | 필수 | 비고 |
|------|---------------|------|-----------|------|------|
| 1 | `order_no` | 수주번호 | text | ✅ | 자동채번 또는 수동입력 |
| 2 | `order_date` | 수주일 | date | ✅ | 기본값: 오늘 |
| 3 | `partner_id` | 거래처 | select | ✅ | 거래처 목록에서 선택 |
| 4 | `part_code` | 품목코드 | text | ✅ | 품목 검색 |
| 5 | `part_name` | 품명 | text | | 품목코드 선택 시 자동 입력 |
| 6 | `spec` | 규격 | text | | 품목코드 선택 시 자동 입력 |
| 7 | `material` | 재질 | text | | |
| 8 | `unit` | 단위 | select | | 옵션: EA, kg, L, Box 등 |
| 9 | `order_qty` | 수주수량 | number | ✅ | |
| 10 | `unit_price` | 단가 | number | ✅ | |
| 11 | `due_date` | 납기일 | date | | |
| 12 | `status` | 상태 | select | ✅ | 옵션: 수주, 진행중, 완료, 취소 |
| 13 | `delivery_partner_id` | 납품처 | text | | |
| 14 | `delivery_address` | 납품장소 | text | | |
| 15 | `shipping_method` | 배송방법 | select | | 옵션: 택배, 화물, 직송, 퀵서비스 등 |
| 16 | `manager_name` | 담당자 | text | | |
| 17 | `memo` | 메모 | textarea | | |
#### Step 3: 버튼 배치
| 버튼 | 액션 타입 | 스타일 | 설정 |
|------|-----------|--------|------|
| 저장 | `저장` | primary | 저장 후 모달 닫기, 부모 화면 테이블 새로고침 |
| 취소 | `모달 닫기` | secondary | |
---
## 4. 컴포넌트 연동 설정
### 4.1 이벤트 흐름
```
[검색 입력]
v2-table-search-widget
│ onFilterChange
v2-table-list (자동 재조회)
[데이터 표시]
v2-aggregation-widget (통계 업데이트)
[수주등록/수정 버튼 클릭]
[모달 열기] → [폼 입력] → [저장]
│ │
│ ▼
│ refreshTable 이벤트
│ │
└────────────────────────┘
v2-table-list (재조회)
v2-aggregation-widget (통계 업데이트)
```
### 4.2 연동 설정
| 소스 컴포넌트 | 이벤트/액션 | 대상 컴포넌트 | 동작 |
|---------------|-------------|---------------|------|
| 검색 위젯 | onFilterChange | 테이블 리스트 | 필터 적용, 재조회 |
| 테이블 리스트 | onDataChange | 집계 위젯 | 통계 업데이트 |
| 수주등록 버튼 | click | 모달 | 빈 폼으로 모달 열기 |
| 수정 버튼 | click | 모달 | 선택 데이터가 채워진 폼 열기 (수정) |
| 삭제 버튼 | click | 테이블 리스트 | 선택 항목 삭제 |
| 출하계획 버튼 | click | 슬라이드 패널 | 선택 항목 기반 출하계획 생성 |
| 모달 저장 | afterSave | 테이블 리스트 | refreshTable |
---
## 5. 사용자 사용 예시 시나리오
### 시나리오 1: 수주 조회
| 단계 | 사용자 동작 | 기대 결과 |
|------|-------------|-----------|
| 1 | 화면 진입 | 전체 수주 목록 표시, 통계(총 금액, 총 수량) 표시 |
| 2 | 거래처 필터를 "삼성전자"로 선택 | 자동 필터링, 통계 업데이트 |
| 3 | 상태를 "진행중"으로 선택 | 추가 필터링 |
| 4 | Group by에서 "거래처" 선택 | 거래처별 그룹핑 표시 |
### 시나리오 2: 수주 등록
| 단계 | 사용자 동작 | 기대 결과 |
|------|-------------|-----------|
| 1 | [수주 등록] 버튼 클릭 | 빈 폼 모달 표시 |
| 2 | 거래처 선택, 품목코드 입력 | 품명, 규격 자동 입력 |
| 3 | 수주수량, 단가 입력 | 금액 자동 계산 |
| 4 | [저장] 버튼 클릭 | 저장 완료, 모달 닫힘, 목록 갱신, 통계 업데이트 |
### 시나리오 3: 수주 수정
| 단계 | 사용자 동작 | 기대 결과 |
|------|-------------|-----------|
| 1 | 테이블에서 행 체크박스 선택 | 행 선택 표시 |
| 2 | [수정] 버튼 클릭 | 수정 모달 표시 (기존 데이터 로드) |
| 3 | 데이터 수정 | 필드 값 변경 |
| 4 | [저장] 버튼 클릭 | 저장 완료, 목록 갱신 |
### 시나리오 4: 수주 삭제
| 단계 | 사용자 동작 | 기대 결과 |
|------|-------------|-----------|
| 1 | 삭제할 행 체크박스 선택 (다중 가능) | 행 선택 표시 |
| 2 | [삭제] 버튼 클릭 | 삭제 확인 다이얼로그 표시 |
| 3 | 확인 | 삭제 완료, 목록 갱신, 통계 업데이트 |
### 시나리오 5: 출하계획 생성
| 단계 | 사용자 동작 | 기대 결과 |
|------|-------------|-----------|
| 1 | 출하할 수주 행 체크박스 선택 (다중) | 행 선택 표시 |
| 2 | [출하계획] 버튼 클릭 | 출하계획 슬라이드 패널 열림 |
| 3 | 출하 수량 입력, 출하일 선택 | 출하계획 데이터 설정 |
| 4 | [적용] 버튼 클릭 | 출하계획 저장, 수주 데이터 업데이트 |
---
## 6. 검증 체크리스트
### 기본 기능
- [ ] 데이터 조회가 정상 동작하는가?
- [ ] 검색 필터 (수주번호, 거래처, 품목명, 상태, 수주일)가 정상 동작하는가?
- [ ] 신규 등록이 정상 동작하는가?
- [ ] 수정이 정상 동작하는가?
- [ ] 삭제가 정상 동작하는가?
- [ ] 엑셀 업로드가 정상 동작하는가?
- [ ] 엑셀 다운로드가 정상 동작하는가?
### 테이블 기능
- [ ] 페이지네이션이 정상 동작하는가?
- [ ] 정렬이 정상 동작하는가?
- [ ] 컬럼 너비 조정이 정상 동작하는가?
- [ ] 체크박스 선택이 정상 동작하는가?
- [ ] 수평 스크롤이 정상 동작하는가?
### 검색 위젯 연동
- [ ] v2-table-search-widget과 v2-table-list 연동이 정상 동작하는가?
- [ ] 필터 변경 시 자동 재조회가 동작하는가?
- [ ] 초기화 버튼이 정상 동작하는가?
### 그룹핑 기능
- [ ] Group by 선택 시 그룹핑이 정상 동작하는가?
- [ ] 그룹 헤더에 건수, 수량, 금액이 표시되는가?
- [ ] 그룹 접기/펼치기가 정상 동작하는가?
### 통계 기능
- [ ] 총 금액이 정확히 계산되는가?
- [ ] 총 수량이 정확히 계산되는가?
- [ ] 필터링 시 통계가 업데이트되는가?
### 출하계획 연동
- [ ] 선택한 수주를 기반으로 출하계획을 생성할 수 있는가?
- [ ] 출하계획 적용 후 수주 데이터가 업데이트되는가?
---
## 7. 참고 사항
### 관련 테이블
- `customer_mng` - 거래처 정보 (partner_id 참조)
- `item_info` - 품목 정보 (part_code 참조)
- `sales_order_detail` - 수주 상세 (다중 품목 관리 시)
- `shipment_mng` - 출하 정보 (출하계획 연동)
### 특이 사항
- `partner_id`는 거래처 테이블의 ID를 참조 (조인 필요)
- `balance_qty` = `order_qty` - `ship_qty` (잔량 자동 계산)
- `total_amount` = `order_qty` × `unit_price` (금액 자동 계산)
- 상태별 뱃지 색상:
- 수주: 파란색 (#dbeafe, #1e40af)
- 진행중: 노란색 (#fef3c7, #92400e)
- 완료: 초록색 (#d1fae5, #065f46)
- 취소: 빨간색 (#fee2e2, #991b1b)
- 수출용 필드: incoterms, payment_term, port_of_loading, port_of_discharge, hs_code, currency
---
## 8. DB INSERT용 JSON 설정 (screen_layouts_v2 방식)
> 📌 실제 화면 저장은 `screen_definitions` + `screen_layouts_v2` 테이블을 사용합니다.
> ⚠️ **주의: 아래 JSON은 "수주관리" 화면 전용 예시입니다!**
### 8.1 화면 정의 (screen_definitions)
**필수 입력 필드:**
```json
{
"screenName": "수주관리",
"tableName": "sales_order_mng",
"companyCode": "COMPANY_7",
"description": "수주 관리 화면"
}
```
### 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": 800 },
"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": "order_no", "searchable": true, "displayName": "수주번호" },
{ "align": "left", "order": 1, "format": "text", "visible": true, "sortable": true, "columnName": "partner_id", "searchable": true, "displayName": "거래처" },
{ "align": "left", "order": 2, "format": "text", "visible": true, "sortable": true, "columnName": "part_code", "searchable": true, "displayName": "품목코드" },
{ "align": "left", "order": 3, "format": "text", "visible": true, "sortable": true, "columnName": "part_name", "searchable": true, "displayName": "품명" },
{ "align": "left", "order": 4, "format": "text", "visible": true, "sortable": true, "columnName": "spec", "searchable": true, "displayName": "규격" },
{ "align": "left", "order": 5, "format": "text", "visible": true, "sortable": true, "columnName": "material", "searchable": true, "displayName": "재질" },
{ "align": "right", "order": 6, "format": "number", "visible": true, "sortable": true, "columnName": "order_qty", "searchable": false, "displayName": "수주수량" },
{ "align": "right", "order": 7, "format": "number", "visible": true, "sortable": true, "columnName": "ship_qty", "searchable": false, "displayName": "출하수량" },
{ "align": "right", "order": 8, "format": "number", "visible": true, "sortable": true, "columnName": "balance_qty", "searchable": false, "displayName": "잔량" },
{ "align": "right", "order": 9, "format": "number", "visible": true, "sortable": true, "columnName": "inventory_qty", "searchable": false, "displayName": "현재고" },
{ "align": "right", "order": 10, "format": "number", "visible": true, "sortable": true, "columnName": "plan_ship_qty", "searchable": false, "displayName": "출하계획량" },
{ "align": "right", "order": 11, "format": "number", "visible": true, "sortable": true, "columnName": "unit_price", "searchable": false, "displayName": "단가" },
{ "align": "right", "order": 12, "format": "number", "visible": true, "sortable": true, "columnName": "total_amount", "searchable": false, "displayName": "금액" },
{ "align": "left", "order": 13, "format": "text", "visible": true, "sortable": true, "columnName": "delivery_partner_id", "searchable": true, "displayName": "납품처" },
{ "align": "left", "order": 14, "format": "text", "visible": true, "sortable": true, "columnName": "delivery_address", "searchable": true, "displayName": "납품장소" },
{ "align": "center", "order": 15, "format": "text", "visible": true, "sortable": true, "columnName": "shipping_method", "searchable": true, "displayName": "배송방법" },
{ "align": "center", "order": 16, "format": "date", "visible": true, "sortable": true, "columnName": "due_date", "searchable": false, "displayName": "납기일" },
{ "align": "center", "order": 17, "format": "date", "visible": true, "sortable": true, "columnName": "order_date", "searchable": false, "displayName": "수주일" },
{ "align": "center", "order": 18, "format": "text", "visible": true, "sortable": true, "columnName": "status", "searchable": true, "displayName": "상태" },
{ "align": "left", "order": 19, "format": "text", "visible": true, "sortable": true, "columnName": "manager_name", "searchable": true, "displayName": "담당자" },
{ "align": "left", "order": 20, "format": "text", "visible": true, "sortable": true, "columnName": "memo", "searchable": true, "displayName": "메모" }
],
"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": "sales_order_mng",
"webTypeConfig": {},
"horizontalScroll": { "enabled": true, "maxColumnWidth": 300, "minColumnWidth": 80, "maxVisibleColumns": 10 }
},
"displayOrder": 1
},
{
"id": "comp_btn_user_options",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 100, "height": 40 },
"position": { "x": 1500, "y": 30, "z": 1 },
"overrides": {
"text": "사용자옵션",
"type": "v2-button-primary",
"label": "사용자옵션 버튼",
"action": { "type": "custom" },
"variant": "secondary",
"actionType": "button",
"webTypeConfig": { "variant": "secondary", "actionType": "custom" }
},
"displayOrder": 2
},
{
"id": "comp_btn_upload",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 100, "height": 40 },
"position": { "x": 1610, "y": 30, "z": 1 },
"overrides": {
"text": "엑셀 업로드",
"type": "v2-button-primary",
"label": "엑셀 업로드 버튼",
"action": { "type": "excel_upload" },
"variant": "secondary",
"actionType": "button",
"webTypeConfig": { "variant": "secondary", "actionType": "custom" }
},
"displayOrder": 3
},
{
"id": "comp_btn_download",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 100, "height": 40 },
"position": { "x": 1720, "y": 30, "z": 1 },
"overrides": {
"text": "엑셀 다운로드",
"type": "v2-button-primary",
"label": "엑셀 다운로드 버튼",
"action": { "type": "excel_download" },
"variant": "secondary",
"actionType": "button",
"webTypeConfig": { "variant": "secondary", "actionType": "custom" }
},
"displayOrder": 4
},
{
"id": "comp_btn_register",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 100, "height": 40 },
"position": { "x": 1500, "y": 100, "z": 1 },
"overrides": {
"text": "수주 등록",
"type": "v2-button-primary",
"label": "수주 등록 버튼",
"action": {
"type": "modal",
"modalSize": "lg",
"modalTitle": "수주 등록",
"targetScreenId": "{{modal_screen_id}}",
"successMessage": "저장되었습니다.",
"errorMessage": "저장 중 오류가 발생했습니다."
},
"variant": "success",
"actionType": "button",
"webTypeConfig": { "variant": "default", "actionType": "custom" }
},
"displayOrder": 5
},
{
"id": "comp_btn_edit",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 80, "height": 40 },
"position": { "x": 1610, "y": 100, "z": 1 },
"overrides": {
"text": "수정",
"type": "v2-button-primary",
"label": "수정 버튼",
"action": {
"type": "edit",
"modalSize": "lg",
"modalTitle": "수주 수정",
"targetScreenId": "{{modal_screen_id}}",
"successMessage": "수정되었습니다.",
"errorMessage": "수정 중 오류가 발생했습니다."
},
"variant": "secondary",
"actionType": "button",
"webTypeConfig": { "variant": "secondary", "actionType": "custom" }
},
"displayOrder": 6
},
{
"id": "comp_btn_delete",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 80, "height": 40 },
"position": { "x": 1700, "y": 100, "z": 1 },
"overrides": {
"text": "삭제",
"type": "v2-button-primary",
"label": "삭제 버튼",
"action": {
"type": "delete",
"successMessage": "삭제되었습니다.",
"errorMessage": "삭제 중 오류가 발생했습니다."
},
"variant": "secondary",
"actionType": "button",
"webTypeConfig": { "variant": "secondary", "actionType": "custom" }
},
"displayOrder": 7
},
{
"id": "comp_btn_shipment",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 100, "height": 40 },
"position": { "x": 1790, "y": 100, "z": 1 },
"overrides": {
"text": "출하계획",
"type": "v2-button-primary",
"label": "출하계획 버튼",
"action": { "type": "custom" },
"variant": "secondary",
"actionType": "button",
"webTypeConfig": { "variant": "secondary", "actionType": "custom" }
},
"displayOrder": 8
}
]
}
```
### 8.3 모달 화면 (수주 등록/수정)
#### 화면 정의 (필수 입력)
```json
{
"screenName": "수주 등록/수정",
"tableName": "sales_order_mng",
"companyCode": "COMPANY_7",
"description": "수주 등록/수정 폼 화면"
}
```
#### 레이아웃 데이터 (screen_layouts_v2.layout_data)
```json
{
"version": "2.0",
"components": [
{
"id": "comp_order_no",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 20, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "수주번호",
"fieldName": "order_no",
"placeholder": "수주번호를 입력하세요",
"required": true
},
"displayOrder": 0
},
{
"id": "comp_order_date",
"url": "@/lib/registry/components/v2-date",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 20, "z": 1 },
"overrides": {
"type": "v2-date",
"label": "수주일",
"fieldName": "order_date",
"required": true
},
"displayOrder": 1
},
{
"id": "comp_partner_id",
"url": "@/lib/registry/components/v2-select",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 100, "z": 1 },
"overrides": {
"type": "v2-select",
"label": "거래처",
"fieldName": "partner_id",
"required": true,
"config": {
"mode": "dropdown",
"source": "table",
"sourceTable": "customer_mng",
"valueField": "id",
"labelField": "name"
}
},
"displayOrder": 2
},
{
"id": "comp_part_code",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 100, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "품목코드",
"fieldName": "part_code",
"placeholder": "품목코드를 입력하세요",
"required": true
},
"displayOrder": 3
},
{
"id": "comp_part_name",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 180, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "품명",
"fieldName": "part_name",
"placeholder": "품명을 입력하세요"
},
"displayOrder": 4
},
{
"id": "comp_spec",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 180, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "규격",
"fieldName": "spec",
"placeholder": "규격을 입력하세요"
},
"displayOrder": 5
},
{
"id": "comp_material",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 260, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "재질",
"fieldName": "material",
"placeholder": "재질을 입력하세요"
},
"displayOrder": 6
},
{
"id": "comp_order_qty",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 260, "z": 1 },
"overrides": {
"type": "v2-input",
"inputType": "number",
"label": "수주수량",
"fieldName": "order_qty",
"placeholder": "수주수량을 입력하세요",
"required": true
},
"displayOrder": 7
},
{
"id": "comp_unit_price",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 340, "z": 1 },
"overrides": {
"type": "v2-input",
"inputType": "number",
"label": "단가",
"fieldName": "unit_price",
"placeholder": "단가를 입력하세요",
"required": true
},
"displayOrder": 8
},
{
"id": "comp_due_date",
"url": "@/lib/registry/components/v2-date",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 340, "z": 1 },
"overrides": {
"type": "v2-date",
"label": "납기일",
"fieldName": "due_date"
},
"displayOrder": 9
},
{
"id": "comp_status",
"url": "@/lib/registry/components/v2-select",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 420, "z": 1 },
"overrides": {
"type": "v2-select",
"label": "상태",
"fieldName": "status",
"required": true,
"config": {
"mode": "dropdown",
"source": "static",
"options": [
{ "value": "수주", "label": "수주" },
{ "value": "진행중", "label": "진행중" },
{ "value": "완료", "label": "완료" },
{ "value": "취소", "label": "취소" }
]
}
},
"displayOrder": 10
},
{
"id": "comp_shipping_method",
"url": "@/lib/registry/components/v2-select",
"size": { "width": 300, "height": 60 },
"position": { "x": 340, "y": 420, "z": 1 },
"overrides": {
"type": "v2-select",
"label": "배송방법",
"fieldName": "shipping_method",
"config": {
"mode": "dropdown",
"source": "static",
"options": [
{ "value": "택배", "label": "택배" },
{ "value": "화물", "label": "화물" },
{ "value": "직송", "label": "직송" },
{ "value": "퀵서비스", "label": "퀵서비스" },
{ "value": "해상운송", "label": "해상운송" }
]
}
},
"displayOrder": 11
},
{
"id": "comp_delivery_address",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 620, "height": 60 },
"position": { "x": 20, "y": 500, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "납품장소",
"fieldName": "delivery_address",
"placeholder": "납품장소를 입력하세요"
},
"displayOrder": 12
},
{
"id": "comp_manager_name",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 300, "height": 60 },
"position": { "x": 20, "y": 580, "z": 1 },
"overrides": {
"type": "v2-input",
"label": "담당자",
"fieldName": "manager_name",
"placeholder": "담당자를 입력하세요"
},
"displayOrder": 13
},
{
"id": "comp_memo",
"url": "@/lib/registry/components/v2-input",
"size": { "width": 620, "height": 80 },
"position": { "x": 20, "y": 660, "z": 1 },
"overrides": {
"type": "v2-input",
"inputType": "textarea",
"label": "메모",
"fieldName": "memo",
"placeholder": "메모를 입력하세요"
},
"displayOrder": 14
},
{
"id": "comp_btn_save",
"url": "@/lib/registry/components/v2-button-primary",
"size": { "width": 100, "height": 40 },
"position": { "x": 540, "y": 760, "z": 1 },
"overrides": {
"text": "저장",
"type": "v2-button-primary",
"label": "저장 버튼",
"action": {
"type": "save",
"closeModalAfterSave": true,
"refreshParentTable": true,
"successMessage": "저장되었습니다.",
"errorMessage": "저장 중 오류가 발생했습니다."
},
"variant": "primary",
"actionType": "button"
},
"displayOrder": 15
}
]
}
```
### 8.4 화면 생성 순서 (중요!)
```
1. 모달 화면 생성 (screen_definitions INSERT)
2. 모달 레이아웃 저장 (screen_layouts_v2 INSERT)
3. 메인 화면 생성 (screen_definitions INSERT)
4. 메인 레이아웃 저장 (screen_layouts_v2 INSERT)
└── targetScreenId에 모달 screen_id 사용!
5. (선택) 메뉴에 화면 연결
```
---
## 9. 화면 구현 체크리스트
> 📋 새로운 화면을 구현할 때 아래 체크리스트를 순서대로 확인하세요.
### 9.1 분석 단계
| 체크 | 항목 | 설명 |
|:----:|------|------|
| ☐ | **테이블 구조 분석** | `sales_order_mng` 테이블 스키마 확인 완료 |
| ☐ | **화면 기능 파악** | 조회/등록/수정/삭제, 검색, 필터, 그룹핑, 출하계획 연동 |
| ☐ | **컴포넌트 매핑** | 필요 기능 → V2 컴포넌트 매핑 완료 |
| ☐ | **구현 불가 항목 확인** | 현재 V2 컴포넌트로 구현 가능 |
### 9.2 INSERT 후 검증
| 체크 | 항목 | 설명 |
|:----:|------|------|
| ☐ | **화면 접속 테스트** | `/screens/{screen_id}` URL로 접속 |
| ☐ | **컴포넌트 렌더링 확인** | 모든 컴포넌트가 정상 표시되는지 확인 |
| ☐ | **검색 기능 테스트** | 검색 위젯 동작 확인 |
| ☐ | **테이블 데이터 로드** | 테이블에 데이터 표시되는지 확인 |
| ☐ | **버튼 동작 테스트** | 등록/수정/삭제/출하계획 버튼 동작 확인 |
| ☐ | **모달 폼 테스트** | 모달 열림, 입력 필드 표시, 저장 동작 확인 |
| ☐ | **통계 업데이트** | 총 금액, 총 수량이 정확히 표시되는지 확인 |
---
## 10. 메뉴에 화면 연결하기
> 📋 화면 생성 후, 특정 메뉴에 연결하여 사용자가 접근할 수 있도록 설정합니다.
### 10.1 메뉴 연결 절차
```
1. 대상 메뉴 찾기 (menu_info 테이블에서 objid 확인)
2. screen_menu_assignments 테이블에 할당 레코드 INSERT
3. menu_info 테이블의 menu_url, screen_code 업데이트
4. 연결 결과 확인
```
### 10.2 메뉴 찾기
```sql
-- 메뉴 이름으로 검색
SELECT objid, menu_name_kor, menu_url, screen_code, company_code
FROM menu_info
WHERE menu_name_kor = '55566' -- 메뉴 이름
AND company_code = 'COMPANY_19'; -- 회사 코드
-- 결과 예시:
-- objid: 1769415229091
```
### 10.3 기존 할당 확인 및 제거 (중복 방지)
> ⚠️ **중요**: 새 화면을 할당하기 전에 해당 메뉴에 이미 할당된 화면이 있는지 확인해야 합니다. 중복 할당 시 화면이 정상적으로 표시되지 않을 수 있습니다.
```sql
-- 1. 해당 메뉴에 이미 할당된 화면 확인
SELECT
sma.assignment_id,
sma.screen_id,
sd.screen_name,
sd.screen_code
FROM screen_menu_assignments sma
JOIN screen_definitions sd ON sma.screen_id = sd.screen_id
WHERE sma.menu_objid = '1769415229091'; -- 대상 메뉴 objid
-- 2. 기존 할당이 있다면 삭제
DELETE FROM screen_menu_assignments
WHERE menu_objid = '1769415229091'; -- 모든 기존 할당 삭제
-- 또는 특정 화면만 남기고 삭제
DELETE FROM screen_menu_assignments
WHERE menu_objid = '1769415229091'
AND screen_id != 3733; -- 3733(수주관리)만 남기고 삭제
```
### 10.4 화면-메뉴 할당 INSERT
```sql
-- screen_menu_assignments에 할당 레코드 추가
INSERT INTO screen_menu_assignments (
screen_id,
menu_objid,
company_code,
display_order,
is_active,
created_date
) VALUES (
3733, -- 메인 화면의 screen_id
'1769415229091', -- menu_info의 objid (문자열로 저장)
'COMPANY_19', -- 회사 코드
1, -- 표시 순서
'Y', -- 활성화 여부
NOW()
) RETURNING assignment_id;
```
### 10.6 메뉴 URL 및 screen_code 업데이트 (필수!)
> ⚠️ **중요**: `screen_menu_assignments`에 레코드를 추가해도 `menu_info`의 `menu_url`과 `screen_code`를 업데이트하지 않으면 메뉴 클릭 시 화면이 표시되지 않습니다.
```sql
-- menu_info 테이블의 menu_url, screen_code 업데이트
UPDATE menu_info
SET menu_url = '/screens/3733', -- 화면 URL
screen_code = 'COMPANY_19_SO_MAIN' -- 화면 코드
WHERE objid = 1769415229091;
```
### 10.7 연결 확인
```sql
-- 메뉴-화면 연결 상태 확인
SELECT
mi.objid,
mi.menu_name_kor,
mi.menu_url,
mi.screen_code,
sd.screen_id,
sd.screen_name
FROM menu_info mi
JOIN screen_definitions sd ON mi.screen_code = sd.screen_code
WHERE mi.objid = 1769415229091;
-- 예상 결과:
-- objid: 1769415229091
-- menu_name_kor: 55566
-- menu_url: /screens/3733
-- screen_code: COMPANY_19_SO_MAIN
-- screen_id: 3733
-- screen_name: 수주관리
```
### 10.8 전체 SQL 예시 (수주관리 화면 → 55566 메뉴)
```sql
-- 1. 메뉴 찾기
SELECT objid, menu_name_kor FROM menu_info
WHERE menu_name_kor = '55566' AND company_code = 'COMPANY_19';
-- 결과: objid = 1769415229091
-- 2. 기존 할당 확인 및 삭제 (중복 방지!)
SELECT sma.assignment_id, sma.screen_id, sd.screen_name
FROM screen_menu_assignments sma
JOIN screen_definitions sd ON sma.screen_id = sd.screen_id
WHERE sma.menu_objid = '1769415229091';
-- 기존 할당이 있다면 삭제
DELETE FROM screen_menu_assignments
WHERE menu_objid = '1769415229091';
-- 3. 새 화면 할당
INSERT INTO screen_menu_assignments (screen_id, menu_objid, company_code, display_order, is_active, created_date)
VALUES (3733, '1769415229091', 'COMPANY_19', 1, 'Y', NOW());
-- 4. 메뉴 URL 업데이트 (필수!)
UPDATE menu_info
SET menu_url = '/screens/3733',
screen_code = 'COMPANY_19_SO_MAIN'
WHERE objid = 1769415229091;
```
### 10.9 메뉴 연결 체크리스트
| 체크 | 항목 | 설명 |
|:----:|------|------|
| ☐ | **대상 메뉴 확인** | `menu_info`에서 메뉴 objid 확인 |
| ☐ | **기존 할당 확인** | `screen_menu_assignments`에서 중복 할당 여부 확인 |
| ☐ | **기존 할당 삭제** | 중복 할당이 있다면 기존 레코드 DELETE |
| ☐ | **새 화면 할당 INSERT** | `screen_menu_assignments` 테이블에 새 레코드 추가 |
| ☐ | **menu_url 업데이트** | `/screens/{screen_id}` 형식으로 업데이트 |
| ☐ | **screen_code 업데이트** | 화면의 screen_code로 업데이트 |
| ☐ | **메뉴 클릭 테스트** | 해당 회사로 로그인하여 메뉴 클릭 시 화면 표시 확인 |