# 수주관리 (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로 업데이트 | | ☐ | **메뉴 클릭 테스트** | 해당 회사로 로그인하여 메뉴 클릭 시 화면 표시 확인 |