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

54 KiB
Raw Blame History

수주관리 (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)

필수 입력 필드:

{
  "screenName": "수주관리",
  "tableName": "sales_order_mng",
  "companyCode": "COMPANY_7",
  "description": "수주 관리 화면"
}

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

{
  "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 모달 화면 (수주 등록/수정)

화면 정의 (필수 입력)

{
  "screenName": "수주 등록/수정",
  "tableName": "sales_order_mng",
  "companyCode": "COMPANY_7",
  "description": "수주 등록/수정 폼 화면"
}

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

{
  "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 메뉴 찾기

-- 메뉴 이름으로 검색
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 기존 할당 확인 및 제거 (중복 방지)

⚠️ 중요: 새 화면을 할당하기 전에 해당 메뉴에 이미 할당된 화면이 있는지 확인해야 합니다. 중복 할당 시 화면이 정상적으로 표시되지 않을 수 있습니다.

-- 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

-- 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_infomenu_urlscreen_code를 업데이트하지 않으면 메뉴 클릭 시 화면이 표시되지 않습니다.

-- 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 연결 확인

-- 메뉴-화면 연결 상태 확인
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 메뉴)

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