47 KiB
WACE 화면 구현 실행 가이드 (챗봇/AI 에이전트 전용)
최종 업데이트: 2026-03-13 용도: 사용자가 "수주관리 화면 만들어줘"라고 요청하면, 이 문서를 참조하여 SQL을 직접 생성하고 화면을 구현하는 AI 챗봇용 실행 가이드 핵심: 이 문서의 SQL 템플릿을 따라 INSERT하면 화면이 자동으로 생성된다
0. 절대 규칙
- 사용자 업무 화면(수주, 생산, 품질 등)은 React 코드(.tsx) 작성 금지 → DB INSERT로만 구현
- 모든 DB 컬럼은 VARCHAR(500) (날짜 컬럼만 TIMESTAMP)
- 모든 테이블에 기본 5개 컬럼 필수: id, created_date, updated_date, writer, company_code
- 모든 INSERT에 ON CONFLICT 절 필수 (중복 방지)
- 컴포넌트는 반드시 v2- 접두사 사용
- [최우선] 비즈니스 테이블 CREATE TABLE 시 NOT NULL / UNIQUE 제약조건 절대 금지!
왜 DB 레벨 제약조건을 걸면 안 되는가?
이 시스템은 멀티테넌시(Multi-Tenancy) 환경이다. 각 회사(tenant)마다 같은 테이블을 공유하되, 필수값/유니크 규칙이 회사별로 다를 수 있다.
따라서 제약조건은 DB에 직접 거는 것이 아니라, 관리자 메뉴에서 회사별 메타데이터로 논리적으로 제어한다:
- 필수값:
table_type_columns.is_nullable = 'N'→ 애플리케이션 레벨에서 검증- 유니크:
table_type_columns.is_unique = 'Y'→ 애플리케이션 레벨에서 검증DB 레벨에서 NOT NULL이나 UNIQUE를 걸면, 특정 회사에만 적용해야 할 규칙이 모든 회사에 강제되어 멀티테넌시가 깨진다.
허용: 기본 5개 컬럼의
idPRIMARY KEY,DEFAULT값만 DB 레벨에서 설정 금지: 비즈니스 컬럼에NOT NULL,UNIQUE,CHECK,FOREIGN KEY등 DB 제약조건 직접 적용
1. 화면 생성 전체 파이프라인
사용자가 화면을 요청하면 아래 7단계를 순서대로 실행한다.
Step 1: 비즈니스 테이블 CREATE TABLE
Step 2: table_labels INSERT (테이블 라벨)
Step 3: table_type_columns INSERT (컬럼 타입 정의, company_code='*')
Step 4: column_labels INSERT (컬럼 한글 라벨)
Step 5: screen_definitions INSERT → screen_id 획득
Step 6: screen_layouts_v2 INSERT (레이아웃 JSON)
Step 7: menu_info INSERT (메뉴 등록)
선택적 추가 단계:
- 채번 규칙이 필요하면: numbering_rules + numbering_rule_parts INSERT
- 카테고리가 필요하면: table_column_category_values INSERT
- 비즈니스 로직(버튼 액션)이 필요하면: dataflow_diagrams INSERT
2. Step 1: 비즈니스 테이블 생성 (CREATE TABLE)
템플릿
[최우선] 비즈니스 컬럼에 NOT NULL / UNIQUE / CHECK / FOREIGN KEY 제약조건 절대 금지! 멀티테넌시 환경에서 회사별로 규칙이 다르므로,
table_type_columns의is_nullable,is_unique메타데이터로 논리적 제어한다.
CREATE TABLE "{테이블명}" (
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
"created_date" timestamp DEFAULT now(),
"updated_date" timestamp DEFAULT now(),
"writer" varchar(500) DEFAULT NULL,
"company_code" varchar(500),
"{비즈니스_컬럼1}" varchar(500),
"{비즈니스_컬럼2}" varchar(500),
"{비즈니스_컬럼3}" varchar(500)
-- NOT NULL, UNIQUE, CHECK, FOREIGN KEY 금지!
);
마스터-디테일인 경우 (2개 테이블)
-- 마스터 테이블
CREATE TABLE "{마스터_테이블}" (
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
"created_date" timestamp DEFAULT now(),
"updated_date" timestamp DEFAULT now(),
"writer" varchar(500) DEFAULT NULL,
"company_code" varchar(500),
"{컬럼1}" varchar(500),
"{컬럼2}" varchar(500)
-- NOT NULL, UNIQUE, FOREIGN KEY 금지!
);
-- 디테일 테이블
CREATE TABLE "{디테일_테이블}" (
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
"created_date" timestamp DEFAULT now(),
"updated_date" timestamp DEFAULT now(),
"writer" varchar(500) DEFAULT NULL,
"company_code" varchar(500),
"{마스터_FK}" varchar(500), -- 마스터 테이블 id 참조 (FOREIGN KEY 제약조건은 걸지 않는다!)
"{컬럼1}" varchar(500),
"{컬럼2}" varchar(500)
-- NOT NULL, UNIQUE, FOREIGN KEY 금지!
);
금지 사항:
- INTEGER, NUMERIC, BOOLEAN, TEXT, DATE 등 DB 타입 직접 사용 금지. 반드시 VARCHAR(500).
- 비즈니스 컬럼에 NOT NULL, UNIQUE, CHECK, FOREIGN KEY 등 DB 레벨 제약조건 금지.
3. Step 2: table_labels INSERT
INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ('{테이블명}', '{한글_라벨}', '{설명}', now(), now())
ON CONFLICT (table_name)
DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now();
예시:
INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ('order_master', '수주 마스터', '수주 헤더 정보 관리', now(), now())
ON CONFLICT (table_name)
DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now();
INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date)
VALUES ('order_detail', '수주 상세', '수주 품목별 상세 정보', now(), now())
ON CONFLICT (table_name)
DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now();
4. Step 3: table_type_columns INSERT
company_code = '*'로 등록한다 (전체 공통 설정).
기본 5개 컬럼 (모든 테이블 공통)
INSERT INTO table_type_columns (
table_name, column_name, company_code, input_type, detail_settings,
is_nullable, is_unique, display_order, column_label, description, is_visible,
created_date, updated_date
)
VALUES
('{테이블명}', 'id', '*', 'text', '{}', 'N', 'Y', -5, 'ID', '기본키 (자동생성)', false, now(), now()),
('{테이블명}', 'created_date', '*', 'date', '{}', 'Y', 'N', -4, '생성일시', '레코드 생성일시', false, now(), now()),
('{테이블명}', 'updated_date', '*', 'date', '{}', 'Y', 'N', -3, '수정일시', '레코드 수정일시', false, now(), now()),
('{테이블명}', 'writer', '*', 'text', '{}', 'Y', 'N', -2, '작성자', '레코드 작성자', false, now(), now()),
('{테이블명}', 'company_code', '*', 'text', '{}', 'Y', 'N', -1, '회사코드', '회사 구분 코드', false, now(), now())
ON CONFLICT (table_name, column_name, company_code)
DO UPDATE SET input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings,
is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique,
display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label,
description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now();
비즈니스 컬럼 (display_order 0부터)
INSERT INTO table_type_columns (
table_name, column_name, company_code, input_type, detail_settings,
is_nullable, is_unique, display_order, column_label, description, is_visible,
created_date, updated_date
)
VALUES
('{테이블명}', '{컬럼명}', '*', '{input_type}', '{detail_settings_json}', 'Y', 'N', {순서}, '{한글라벨}', '{설명}', true, now(), now())
ON CONFLICT (table_name, column_name, company_code)
DO UPDATE SET input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings,
is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique,
display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label,
description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now();
input_type 선택 기준
| 데이터 성격 | input_type | detail_settings 예시 |
|---|---|---|
| 일반 텍스트 | text |
'{}' |
| 숫자 (수량, 금액) | number |
'{}' |
| 날짜 | date |
'{}' |
| 여러 줄 텍스트 (비고) | textarea |
'{}' |
| 공통코드 선택 (상태 등) | code |
'{"codeCategory":"STATUS_CODE"}' |
| 다른 테이블 참조 (거래처 등) | entity |
'{"referenceTable":"customer_info","referenceColumn":"id","displayColumn":"customer_name"}' |
| 정적 옵션 선택 | select |
'{"options":[{"label":"옵션1","value":"v1"},{"label":"옵션2","value":"v2"}]}' |
| 체크박스 | checkbox |
'{}' |
| 라디오 | radio |
'{}' |
| 카테고리 | category |
'{"categoryRef":"CAT_ID"}' |
| 자동 채번 | numbering |
'{"numberingRuleId":"rule_id"}' |
5. Step 4: column_labels INSERT
레거시 호환용이지만 필수 등록이다. table_type_columns와 동일한 값을 넣되,
column_label(한글명)을 추가.주의:
column_labels테이블의 UNIQUE 제약조건은(table_name, column_name, company_code)3개 컬럼이다. 반드시company_code를 포함해야 한다.
-- 기본 5개 컬럼
INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date)
VALUES
('{테이블명}', 'id', 'ID', 'text', '{}', '기본키 (자동생성)', -5, true, '*', now(), now()),
('{테이블명}', 'created_date', '생성일시', 'date', '{}', '레코드 생성일시', -4, true, '*', now(), now()),
('{테이블명}', 'updated_date', '수정일시', 'date', '{}', '레코드 수정일시', -3, true, '*', now(), now()),
('{테이블명}', 'writer', '작성자', 'text', '{}', '레코드 작성자', -2, true, '*', now(), now()),
('{테이블명}', 'company_code', '회사코드', 'text', '{}', '회사 구분 코드', -1, true, '*', now(), now())
ON CONFLICT (table_name, column_name, company_code)
DO UPDATE SET column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type,
detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description,
display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now();
-- 비즈니스 컬럼
INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date)
VALUES
('{테이블명}', '{컬럼명}', '{한글라벨}', '{input_type}', '{detail_settings}', '{설명}', {순서}, true, '*', now(), now())
ON CONFLICT (table_name, column_name, company_code)
DO UPDATE SET column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type,
detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description,
display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now();
6. Step 5: screen_definitions INSERT
INSERT INTO screen_definitions (
screen_name, screen_code, table_name, company_code, description, is_active,
db_source_type, data_source_type, created_date
)
VALUES (
'{화면명}', -- 예: '수주관리'
'{screen_code}', -- 예: 'COMPANY_A_ORDER_MNG' (회사코드_식별자)
'{메인_테이블명}', -- 예: 'order_master'
'{company_code}', -- 예: 'COMPANY_A'
'{설명}',
'Y',
'internal',
'database',
now()
)
RETURNING screen_id;
screen_code 규칙: {company_code}_{영문식별자} (예: ILSHIN_ORDER_MNG, COMPANY_19_ITEM_INFO)
중요: Step 6, 7에서 screen_id가 필요하다. 서브쿼리로 참조하면 하드코딩 실수를 방지할 수 있다:
(SELECT screen_id FROM screen_definitions WHERE screen_code = '{screen_code}')
screen_code 조건부 UNIQUE 규칙:
screen_code는 단순 UNIQUE가 아니라WHERE is_active <> 'D'조건부 UNIQUE이다.
- 삭제된 화면(
is_active = 'D')과 동일한 코드로 새 화면을 만들 수 있다.- 활성 상태(
'Y'또는'N')에서는 같은screen_code가 중복되면 에러가 발생한다.- 화면 삭제 시
DELETE가 아닌UPDATE SET is_active = 'D'로 소프트 삭제하므로, 이전 코드의 재사용이 가능하다.
7. Step 6: screen_layouts_v2 INSERT (핵심)
기본 구조
INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layer_name, layout_data, created_at, updated_at)
VALUES (
(SELECT screen_id FROM screen_definitions WHERE screen_code = '{screen_code}'),
'{company_code}',
1, -- 기본 레이어
'기본 레이어',
'{layout_data_json}'::jsonb,
now(),
now()
)
ON CONFLICT (screen_id, company_code, layer_id)
DO UPDATE SET layout_data = EXCLUDED.layout_data, updated_at = now();
layout_data JSON 뼈대
{
"version": "2.0",
"components": [
{
"id": "{고유ID}",
"url": "@/lib/registry/components/{컴포넌트타입}",
"position": { "x": 0, "y": 0 },
"size": { "width": 1920, "height": 800 },
"displayOrder": 0,
"overrides": { /* 컴포넌트별 설정 */ }
}
],
"gridSettings": { "columns": 12, "gap": 16, "padding": 16 },
"screenResolution": { "width": 1920, "height": 1080 }
}
컴포넌트 url 매핑표
| 컴포넌트 | url 값 |
|---|---|
| v2-table-list | @/lib/registry/components/v2-table-list |
| v2-table-search-widget | @/lib/registry/components/v2-table-search-widget |
| v2-split-panel-layout | @/lib/registry/components/v2-split-panel-layout |
| v2-table-grouped | @/lib/registry/components/v2-table-grouped |
| v2-tabs-widget | @/lib/registry/components/v2-tabs-widget |
| v2-button-primary | @/lib/registry/components/v2-button-primary |
| v2-input | @/lib/registry/components/v2-input |
| v2-select | @/lib/registry/components/v2-select |
| v2-date | @/lib/registry/components/v2-date |
| v2-card-display | @/lib/registry/components/v2-card-display |
| v2-pivot-grid | @/lib/registry/components/v2-pivot-grid |
| v2-timeline-scheduler | @/lib/registry/components/v2-timeline-scheduler |
| v2-text-display | @/lib/registry/components/v2-text-display |
| v2-aggregation-widget | @/lib/registry/components/v2-aggregation-widget |
| v2-numbering-rule | @/lib/registry/components/v2-numbering-rule |
| v2-file-upload | @/lib/registry/components/v2-file-upload |
| v2-section-card | @/lib/registry/components/v2-section-card |
| v2-divider-line | @/lib/registry/components/v2-divider-line |
| v2-bom-tree | @/lib/registry/components/v2-bom-tree |
| v2-approval-step | @/lib/registry/components/v2-approval-step |
| v2-status-count | @/lib/registry/components/v2-status-count |
| v2-section-paper | @/lib/registry/components/v2-section-paper |
| v2-split-line | @/lib/registry/components/v2-split-line |
| v2-repeat-container | @/lib/registry/components/v2-repeat-container |
| v2-repeater | @/lib/registry/components/v2-repeater |
| v2-category-manager | @/lib/registry/components/v2-category-manager |
| v2-media | @/lib/registry/components/v2-media |
| v2-location-swap-selector | @/lib/registry/components/v2-location-swap-selector |
| v2-rack-structure | @/lib/registry/components/v2-rack-structure |
| v2-process-work-standard | @/lib/registry/components/v2-process-work-standard |
| v2-item-routing | @/lib/registry/components/v2-item-routing |
| v2-bom-item-editor | @/lib/registry/components/v2-bom-item-editor |
8. 패턴별 layout_data 완전 예시
8.1 패턴 A: 기본 마스터 (검색 + 테이블)
사용 조건: 단일 테이블 CRUD, 마스터-디테일 관계 없음
{
"version": "2.0",
"components": [
{
"id": "search_1",
"url": "@/lib/registry/components/v2-table-search-widget",
"position": { "x": 0, "y": 0 },
"size": { "width": 1920, "height": 100 },
"displayOrder": 0,
"overrides": {
"label": "검색",
"autoSelectFirstTable": true,
"showTableSelector": false
}
},
{
"id": "table_1",
"url": "@/lib/registry/components/v2-table-list",
"position": { "x": 0, "y": 120 },
"size": { "width": 1920, "height": 700 },
"displayOrder": 1,
"overrides": {
"label": "{화면제목}",
"tableName": "{테이블명}",
"autoLoad": true,
"displayMode": "table",
"checkbox": { "enabled": true, "multiple": true, "position": "left", "selectAll": true },
"pagination": { "enabled": true, "pageSize": 20, "showSizeSelector": true, "showPageInfo": true },
"horizontalScroll": { "enabled": true, "maxVisibleColumns": 8 },
"toolbar": { "showEditMode": true, "showExcel": true, "showRefresh": true }
}
}
],
"gridSettings": { "columns": 12, "gap": 16, "padding": 16 },
"screenResolution": { "width": 1920, "height": 1080 }
}
8.2 패턴 B: 마스터-디테일 (좌우 분할)
사용 조건: 좌측 마스터 테이블 선택 → 우측 디테일 테이블 연동. 두 테이블 간 FK 관계.
{
"version": "2.0",
"components": [
{
"id": "split_1",
"url": "@/lib/registry/components/v2-split-panel-layout",
"position": { "x": 0, "y": 0 },
"size": { "width": 1920, "height": 850 },
"displayOrder": 0,
"overrides": {
"label": "{화면제목}",
"splitRatio": 35,
"resizable": true,
"autoLoad": true,
"syncSelection": true,
"leftPanel": {
"title": "{마스터_제목}",
"displayMode": "table",
"tableName": "{마스터_테이블명}",
"showSearch": true,
"showAdd": true,
"showEdit": false,
"showDelete": true,
"columns": [
{ "name": "{컬럼1}", "label": "{라벨1}", "width": 120, "sortable": true },
{ "name": "{컬럼2}", "label": "{라벨2}", "width": 150 },
{ "name": "{컬럼3}", "label": "{라벨3}", "width": 100 }
],
"addButton": { "enabled": true, "mode": "auto" },
"deleteButton": { "enabled": true, "confirmMessage": "선택한 항목을 삭제하시겠습니까?" }
},
"rightPanel": {
"title": "{디테일_제목}",
"displayMode": "table",
"tableName": "{디테일_테이블명}",
"relation": {
"type": "detail",
"leftColumn": "id",
"rightColumn": "{마스터FK_컬럼}",
"foreignKey": "{마스터FK_컬럼}"
},
"columns": [
{ "name": "{컬럼1}", "label": "{라벨1}", "width": 120 },
{ "name": "{컬럼2}", "label": "{라벨2}", "width": 150 },
{ "name": "{컬럼3}", "label": "{라벨3}", "width": 100, "editable": true }
],
"addButton": { "enabled": true, "mode": "auto" },
"editButton": { "enabled": true, "mode": "auto" },
"deleteButton": { "enabled": true, "confirmMessage": "삭제하시겠습니까?" }
}
}
}
],
"gridSettings": { "columns": 12, "gap": 16, "padding": 16 },
"screenResolution": { "width": 1920, "height": 1080 }
}
8.3 패턴 C: 마스터-디테일 + 탭
사용 조건: 패턴 B에서 우측에 여러 종류의 상세를 탭으로 구분
패턴 B의 rightPanel에 additionalTabs 추가:
{
"rightPanel": {
"title": "{디테일_제목}",
"displayMode": "table",
"tableName": "{기본탭_테이블}",
"relation": {
"type": "detail",
"leftColumn": "id",
"rightColumn": "{FK_컬럼}",
"foreignKey": "{FK_컬럼}"
},
"additionalTabs": [
{
"tabId": "tab_basic",
"label": "기본정보",
"tableName": "{기본정보_테이블}",
"displayMode": "table",
"relation": { "type": "detail", "leftColumn": "id", "rightColumn": "{FK}", "foreignKey": "{FK}" },
"columns": [ /* 컬럼 배열 */ ],
"addButton": { "enabled": true },
"deleteButton": { "enabled": true }
},
{
"tabId": "tab_history",
"label": "이력",
"tableName": "{이력_테이블}",
"displayMode": "table",
"relation": { "type": "detail", "leftColumn": "id", "rightColumn": "{FK}", "foreignKey": "{FK}" },
"columns": [ /* 컬럼 배열 */ ]
},
{
"tabId": "tab_files",
"label": "첨부파일",
"tableName": "{파일_테이블}",
"displayMode": "table",
"relation": { "type": "detail", "leftColumn": "id", "rightColumn": "{FK}", "foreignKey": "{FK}" },
"columns": [ /* 컬럼 배열 */ ]
}
]
}
}
8.4 패턴 D: 그룹화 테이블
{
"version": "2.0",
"components": [
{
"id": "grouped_1",
"url": "@/lib/registry/components/v2-table-grouped",
"position": { "x": 0, "y": 0 },
"size": { "width": 1920, "height": 800 },
"displayOrder": 0,
"overrides": {
"label": "{화면제목}",
"selectedTable": "{테이블명}",
"groupConfig": {
"groupByColumn": "{그룹기준_컬럼}",
"groupLabelFormat": "{value}",
"defaultExpanded": true,
"sortDirection": "asc",
"summary": { "showCount": true, "sumColumns": ["{합계컬럼1}", "{합계컬럼2}"] }
},
"columns": [
{ "columnName": "{컬럼1}", "displayName": "{라벨1}", "visible": true, "width": 120 },
{ "columnName": "{컬럼2}", "displayName": "{라벨2}", "visible": true, "width": 150 }
],
"showCheckbox": true,
"showExpandAllButton": true
}
}
],
"gridSettings": { "columns": 12, "gap": 16, "padding": 16 },
"screenResolution": { "width": 1920, "height": 1080 }
}
8.5 패턴 E: 타임라인/간트차트
{
"version": "2.0",
"components": [
{
"id": "timeline_1",
"url": "@/lib/registry/components/v2-timeline-scheduler",
"position": { "x": 0, "y": 0 },
"size": { "width": 1920, "height": 800 },
"displayOrder": 0,
"overrides": {
"label": "{화면제목}",
"selectedTable": "{스케줄_테이블}",
"resourceTable": "{리소스_테이블}",
"fieldMapping": {
"id": "id",
"resourceId": "{리소스FK_컬럼}",
"title": "{제목_컬럼}",
"startDate": "{시작일_컬럼}",
"endDate": "{종료일_컬럼}",
"status": "{상태_컬럼}",
"progress": "{진행률_컬럼}"
},
"resourceFieldMapping": {
"id": "id",
"name": "{리소스명_컬럼}",
"group": "{그룹_컬럼}"
},
"defaultZoomLevel": "day",
"editable": true,
"allowDrag": true,
"allowResize": true
}
}
],
"gridSettings": { "columns": 12, "gap": 16, "padding": 16 },
"screenResolution": { "width": 1920, "height": 1080 }
}
9. Step 7: menu_info INSERT
INSERT INTO menu_info (
objid, menu_type, parent_obj_id,
menu_name_kor, menu_name_eng, seq,
menu_url, menu_desc, writer, regdate, status,
company_code, screen_code
)
VALUES (
{고유_objid},
0,
{부모_메뉴_objid},
'{메뉴명_한글}',
'{메뉴명_영문}',
{정렬순서},
'/screen/{screen_code}',
'{메뉴_설명}',
'admin',
now(),
'active',
'{company_code}',
'{screen_code}'
);
objid: BIGINT 고유값.extract(epoch from now())::bigint * 1000으로 생성menu_type:0= 말단 메뉴(화면),1= 폴더parent_obj_id: 상위 폴더 메뉴의 objid
objid 생성 규칙 및 주의사항:
기본 생성: extract(epoch from now())::bigint * 1000
여러 메뉴를 한 트랜잭션에서 동시에 INSERT할 때 PK 중복 위험!
now()는 같은 트랜잭션 안에서 동일한 값을 반환하므로, 복수 INSERT 시 objid가 충돌한다. 반드시 순서값을 더해서 고유성을 보장할 것:-- 폴더 메뉴 extract(epoch from now())::bigint * 1000 + 1 -- 화면 메뉴 1 extract(epoch from now())::bigint * 1000 + 2 -- 화면 메뉴 2 extract(epoch from now())::bigint * 1000 + 3
10. 선택적 단계: 채번 규칙 설정
자동으로 코드/번호를 생성해야 하는 컬럼이 있을 때 사용.
numbering_rules INSERT
INSERT INTO numbering_rules (
rule_id, rule_name, description, separator, reset_period,
current_sequence, table_name, column_name, company_code,
created_at, updated_at, created_by
)
VALUES (
'{rule_id}', -- 예: 'ORDER_NO_RULE'
'{규칙명}', -- 예: '수주번호 채번'
'{설명}',
'-', -- 구분자
'year', -- 'none', 'year', 'month', 'day'
1, -- 시작 순번
'{테이블명}', -- 예: 'order_master'
'{컬럼명}', -- 예: 'order_no'
'{company_code}',
now(), now(), 'admin'
);
numbering_rule_parts INSERT (채번 구성 파트)
-- 파트 1: 접두사
INSERT INTO numbering_rule_parts (rule_id, part_order, part_type, generation_method, auto_config, manual_config, company_code, created_at)
VALUES ('{rule_id}', 1, 'prefix', 'auto', '{"prefix": "SO", "separatorAfter": "-"}'::jsonb, '{}'::jsonb, '{company_code}', now());
-- 파트 2: 날짜
INSERT INTO numbering_rule_parts (rule_id, part_order, part_type, generation_method, auto_config, manual_config, company_code, created_at)
VALUES ('{rule_id}', 2, 'date', 'auto', '{"format": "YYYYMM", "separatorAfter": "-"}'::jsonb, '{}'::jsonb, '{company_code}', now());
-- 파트 3: 순번
INSERT INTO numbering_rule_parts (rule_id, part_order, part_type, generation_method, auto_config, manual_config, company_code, created_at)
VALUES ('{rule_id}', 3, 'sequence', 'auto', '{"digits": 4, "startFrom": 1}'::jsonb, '{}'::jsonb, '{company_code}', now());
결과: SO-202603-0001, SO-202603-0002, ...
11. 선택적 단계: 카테고리 값 설정
상태, 유형 등을 카테고리로 관리할 때 사용.
table_column_category_values INSERT
INSERT INTO table_column_category_values (
table_name, column_name, value_code, value_label, value_order,
parent_value_id, depth, description, color, company_code, created_by
)
VALUES
('{테이블명}', '{컬럼명}', 'ACTIVE', '활성', 1, NULL, 1, '활성 상태', '#22c55e', '{company_code}', 'admin'),
('{테이블명}', '{컬럼명}', 'INACTIVE', '비활성', 2, NULL, 1, '비활성 상태', '#ef4444', '{company_code}', 'admin'),
('{테이블명}', '{컬럼명}', 'PENDING', '대기', 3, NULL, 1, '승인 대기', '#f59e0b', '{company_code}', 'admin');
12. 패턴 판단 의사결정 트리
사용자가 화면을 요청하면 이 트리로 패턴을 결정한다.
Q1. 시간축 기반 일정/간트차트가 필요한가?
├─ YES → 패턴 E (타임라인) → v2-timeline-scheduler
└─ NO ↓
Q2. 다차원 집계/피벗 분석이 필요한가?
├─ YES → 피벗 → v2-pivot-grid
└─ NO ↓
Q3. 데이터를 그룹별로 접기/펼치기가 필요한가?
├─ YES → 패턴 D (그룹화) → v2-table-grouped
└─ NO ↓
Q4. 이미지+정보를 카드 형태로 표시하는가?
├─ YES → 카드뷰 → v2-card-display
└─ NO ↓
Q5. 마스터 테이블 선택 시 연관 디테일이 필요한가?
├─ YES → Q5-1. 디테일에 여러 탭이 필요한가?
│ ├─ YES → 패턴 C (마스터-디테일+탭) → v2-split-panel-layout + additionalTabs
│ └─ NO → 패턴 B (마스터-디테일) → v2-split-panel-layout
└─ NO → 패턴 A (기본 마스터) → v2-table-search-widget + v2-table-list
13. 화면 간 연결 관계 정의
13.1 마스터-디테일 관계 (v2-split-panel-layout)
좌측 마스터 테이블의 행을 선택하면, 우측 디테일 테이블이 해당 FK로 필터링된다.
relation 설정:
JSON 안에 주석(
//,/* */) 절대 금지! PostgreSQL::jsonb캐스팅 시 파싱 에러 발생. 설명은 반드시 JSON 바깥에 작성한다.
type:"detail"(FK 관계)leftColumn: 마스터 테이블의 PK 컬럼 (보통"id")rightColumn: 디테일 테이블의 FK 컬럼foreignKey:rightColumn과 동일한 값
{
"relation": {
"type": "detail",
"leftColumn": "id",
"rightColumn": "master_id",
"foreignKey": "master_id"
}
}
복합 키인 경우:
{
"relation": {
"type": "detail",
"keys": [
{ "leftColumn": "order_no", "rightColumn": "order_no" },
{ "leftColumn": "company_code", "rightColumn": "company_code" }
]
}
}
13.2 엔티티 조인 (테이블 참조 표시)
디테일 테이블의 FK 컬럼에 다른 테이블의 이름을 표시하고 싶을 때.
table_type_columns에서 설정:
INSERT INTO table_type_columns (table_name, column_name, company_code, input_type, detail_settings, ...)
VALUES ('order_detail', 'item_id', '*', 'entity',
'{"referenceTable":"item_info","referenceColumn":"id","displayColumn":"item_name"}', ...);
v2-table-list columns에서 설정:
{
"columns": [
{
"name": "item_id",
"label": "품목",
"isEntityJoin": true,
"joinInfo": {
"sourceTable": "order_detail",
"sourceColumn": "item_id",
"referenceTable": "item_info",
"joinAlias": "item_name"
}
}
]
}
13.3 모달 화면 연결
추가/편집 버튼 클릭 시 별도 모달 화면을 띄우는 경우.
- 모달용 screen_definitions INSERT (별도 화면 생성)
- split-panel의 addButton/editButton에서 연결:
{
"addButton": {
"enabled": true,
"mode": "modal",
"modalScreenId": "{모달_screen_id}"
},
"editButton": {
"enabled": true,
"mode": "modal",
"modalScreenId": "{모달_screen_id}"
}
}
14. 비즈니스 로직 설정 (제어관리)
버튼 클릭 시 INSERT/UPDATE/DELETE, 상태 변경, 이력 기록 등이 필요한 경우.
14.1 v2-button-primary overrides
{
"id": "btn_confirm",
"url": "@/lib/registry/components/v2-button-primary",
"position": { "x": 1700, "y": 10 },
"size": { "width": 100, "height": 40 },
"overrides": {
"text": "확정",
"variant": "primary",
"actionType": "button",
"action": { "type": "custom" },
"webTypeConfig": {
"enableDataflowControl": true,
"dataflowConfig": {
"controlMode": "relationship",
"relationshipConfig": {
"relationshipId": "{관계_ID}",
"relationshipName": "{관계명}",
"executionTiming": "after"
}
}
}
}
}
14.2 dataflow_diagrams INSERT
INSERT INTO dataflow_diagrams (
diagram_name, company_code,
relationships, control, plan, node_positions
)
VALUES (
'{관계도명}',
'{company_code}',
'[{"fromTable":"{소스_테이블}","toTable":"{타겟_테이블}","relationType":"data_save"}]'::jsonb,
'[{
"conditions": [{"field":"status","operator":"=","value":"대기","dataType":"string"}],
"triggerType": "update"
}]'::jsonb,
'[{
"actions": [
{
"actionType": "update",
"targetTable": "{타겟_테이블}",
"conditions": [{"field":"status","operator":"=","value":"대기"}],
"fieldMappings": [{"targetField":"status","defaultValue":"확정"}]
},
{
"actionType": "insert",
"targetTable": "{이력_테이블}",
"fieldMappings": [
{"sourceField":"order_no","targetField":"order_no"},
{"targetField":"action","defaultValue":"확정"}
]
}
]
}]'::jsonb,
'[]'::jsonb
)
RETURNING diagram_id;
executionTiming 선택:
before: 메인 액션 전 → 조건 체크 (조건 불충족 시 메인 액션 중단)after: 메인 액션 후 → 후처리 (이력 기록, 상태 변경 등)replace: 메인 액션 대체 → 제어만 실행
15. 전체 예시: "수주관리 화면 만들어줘"
요구사항 해석
- 마스터: order_master (수주번호, 거래처, 수주일자, 상태)
- 디테일: order_detail (품목, 수량, 단가, 금액)
- 패턴: B (마스터-디테일)
실행 SQL
-- ===== Step 1: 테이블 생성 =====
CREATE TABLE "order_master" (
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
"created_date" timestamp DEFAULT now(),
"updated_date" timestamp DEFAULT now(),
"writer" varchar(500) DEFAULT NULL,
"company_code" varchar(500),
"order_no" varchar(500),
"customer_id" varchar(500),
"order_date" varchar(500),
"delivery_date" varchar(500),
"status" varchar(500),
"total_amount" varchar(500),
"notes" varchar(500)
);
CREATE TABLE "order_detail" (
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
"created_date" timestamp DEFAULT now(),
"updated_date" timestamp DEFAULT now(),
"writer" varchar(500) DEFAULT NULL,
"company_code" varchar(500),
"order_master_id" varchar(500),
"item_id" varchar(500),
"quantity" varchar(500),
"unit_price" varchar(500),
"amount" varchar(500),
"notes" varchar(500)
);
-- ===== Step 2: table_labels =====
INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) VALUES
('order_master', '수주 마스터', '수주 헤더 정보', now(), now()),
('order_detail', '수주 상세', '수주 품목별 상세', now(), now())
ON CONFLICT (table_name) DO UPDATE SET table_label = EXCLUDED.table_label, description = EXCLUDED.description, updated_date = now();
-- ===== Step 3: table_type_columns (확장 컬럼 포함) =====
-- order_master 기본 + 비즈니스 컬럼
INSERT INTO table_type_columns (
table_name, column_name, company_code, input_type, detail_settings,
is_nullable, is_unique, display_order, column_label, description, is_visible,
created_date, updated_date
) VALUES
('order_master', 'id', '*', 'text', '{}', 'N', 'Y', -5, 'ID', '기본키', false, now(), now()),
('order_master', 'created_date', '*', 'date', '{}', 'Y', 'N', -4, '생성일시', '레코드 생성일시', false, now(), now()),
('order_master', 'updated_date', '*', 'date', '{}', 'Y', 'N', -3, '수정일시', '레코드 수정일시', false, now(), now()),
('order_master', 'writer', '*', 'text', '{}', 'Y', 'N', -2, '작성자', '레코드 작성자', false, now(), now()),
('order_master', 'company_code', '*', 'text', '{}', 'Y', 'N', -1, '회사코드', '회사 구분 코드', false, now(), now()),
('order_master', 'order_no', '*', 'text', '{}', 'N', 'Y', 0, '수주번호', '수주 식별번호', true, now(), now()),
('order_master', 'customer_id', '*', 'entity', '{"referenceTable":"customer_info","referenceColumn":"id","displayColumn":"customer_name"}', 'N', 'N', 1, '거래처', '거래처 참조', true, now(), now()),
('order_master', 'order_date', '*', 'date', '{}', 'N', 'N', 2, '수주일자', '', true, now(), now()),
('order_master', 'delivery_date', '*', 'date', '{}', 'Y', 'N', 3, '납기일', '', true, now(), now()),
('order_master', 'status', '*', 'code', '{"codeCategory":"ORDER_STATUS"}', 'Y', 'N', 4, '상태', '수주 상태', true, now(), now()),
('order_master', 'total_amount', '*', 'number', '{}', 'Y', 'N', 5, '총금액', '', true, now(), now()),
('order_master', 'notes', '*', 'textarea', '{}', 'Y', 'N', 6, '비고', '', true, now(), now())
ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET
input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings,
is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique,
display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label,
description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now();
-- order_detail 기본 + 비즈니스 컬럼
INSERT INTO table_type_columns (
table_name, column_name, company_code, input_type, detail_settings,
is_nullable, is_unique, display_order, column_label, description, is_visible,
created_date, updated_date
) VALUES
('order_detail', 'id', '*', 'text', '{}', 'N', 'Y', -5, 'ID', '기본키', false, now(), now()),
('order_detail', 'created_date', '*', 'date', '{}', 'Y', 'N', -4, '생성일시', '레코드 생성일시', false, now(), now()),
('order_detail', 'updated_date', '*', 'date', '{}', 'Y', 'N', -3, '수정일시', '레코드 수정일시', false, now(), now()),
('order_detail', 'writer', '*', 'text', '{}', 'Y', 'N', -2, '작성자', '레코드 작성자', false, now(), now()),
('order_detail', 'company_code', '*', 'text', '{}', 'Y', 'N', -1, '회사코드', '회사 구분 코드', false, now(), now()),
('order_detail', 'order_master_id', '*', 'text', '{}', 'N', 'N', 0, '수주마스터ID', 'FK', false, now(), now()),
('order_detail', 'item_id', '*', 'entity', '{"referenceTable":"item_info","referenceColumn":"id","displayColumn":"item_name"}', 'N', 'N', 1, '품목', '품목 참조', true, now(), now()),
('order_detail', 'quantity', '*', 'number', '{}', 'N', 'N', 2, '수량', '', true, now(), now()),
('order_detail', 'unit_price', '*', 'number', '{}', 'Y', 'N', 3, '단가', '', true, now(), now()),
('order_detail', 'amount', '*', 'number', '{}', 'Y', 'N', 4, '금액', '', true, now(), now()),
('order_detail', 'notes', '*', 'textarea', '{}', 'Y', 'N', 5, '비고', '', true, now(), now())
ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET
input_type = EXCLUDED.input_type, detail_settings = EXCLUDED.detail_settings,
is_nullable = EXCLUDED.is_nullable, is_unique = EXCLUDED.is_unique,
display_order = EXCLUDED.display_order, column_label = EXCLUDED.column_label,
description = EXCLUDED.description, is_visible = EXCLUDED.is_visible, updated_date = now();
-- ===== Step 4: column_labels (company_code 필수!) =====
INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date) VALUES
('order_master', 'id', 'ID', 'text', '{}', '기본키', -5, true, '*', now(), now()),
('order_master', 'created_date', '생성일시', 'date', '{}', '', -4, true, '*', now(), now()),
('order_master', 'updated_date', '수정일시', 'date', '{}', '', -3, true, '*', now(), now()),
('order_master', 'writer', '작성자', 'text', '{}', '', -2, true, '*', now(), now()),
('order_master', 'company_code', '회사코드', 'text', '{}', '', -1, true, '*', now(), now()),
('order_master', 'order_no', '수주번호', 'text', '{}', '수주 식별번호', 0, true, '*', now(), now()),
('order_master', 'customer_id', '거래처', 'entity', '{"referenceTable":"customer_info","referenceColumn":"id","displayColumn":"customer_name"}', '거래처 참조', 1, true, '*', now(), now()),
('order_master', 'order_date', '수주일자', 'date', '{}', '', 2, true, '*', now(), now()),
('order_master', 'delivery_date', '납기일', 'date', '{}', '', 3, true, '*', now(), now()),
('order_master', 'status', '상태', 'code', '{"codeCategory":"ORDER_STATUS"}', '수주 상태', 4, true, '*', now(), now()),
('order_master', 'total_amount', '총금액', 'number', '{}', '', 5, true, '*', now(), now()),
('order_master', 'notes', '비고', 'textarea', '{}', '', 6, true, '*', now(), now())
ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET
column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type,
detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description,
display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now();
INSERT INTO column_labels (table_name, column_name, column_label, input_type, detail_settings, description, display_order, is_visible, company_code, created_date, updated_date) VALUES
('order_detail', 'id', 'ID', 'text', '{}', '기본키', -5, true, '*', now(), now()),
('order_detail', 'created_date', '생성일시', 'date', '{}', '', -4, true, '*', now(), now()),
('order_detail', 'updated_date', '수정일시', 'date', '{}', '', -3, true, '*', now(), now()),
('order_detail', 'writer', '작성자', 'text', '{}', '', -2, true, '*', now(), now()),
('order_detail', 'company_code', '회사코드', 'text', '{}', '', -1, true, '*', now(), now()),
('order_detail', 'order_master_id', '수주마스터ID', 'text', '{}', 'FK', 0, true, '*', now(), now()),
('order_detail', 'item_id', '품목', 'entity', '{"referenceTable":"item_info","referenceColumn":"id","displayColumn":"item_name"}', '품목 참조', 1, true, '*', now(), now()),
('order_detail', 'quantity', '수량', 'number', '{}', '', 2, true, '*', now(), now()),
('order_detail', 'unit_price', '단가', 'number', '{}', '', 3, true, '*', now(), now()),
('order_detail', 'amount', '금액', 'number', '{}', '', 4, true, '*', now(), now()),
('order_detail', 'notes', '비고', 'textarea', '{}', '', 5, true, '*', now(), now())
ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET
column_label = EXCLUDED.column_label, input_type = EXCLUDED.input_type,
detail_settings = EXCLUDED.detail_settings, description = EXCLUDED.description,
display_order = EXCLUDED.display_order, is_visible = EXCLUDED.is_visible, updated_date = now();
-- ===== Step 5: screen_definitions =====
INSERT INTO screen_definitions (screen_name, screen_code, table_name, company_code, description, is_active, db_source_type, data_source_type, created_date)
VALUES ('수주관리', 'ILSHIN_ORDER_MNG', 'order_master', 'ILSHIN', '수주 마스터-디테일 관리', 'Y', 'internal', 'database', now());
-- ===== Step 6: screen_layouts_v2 (서브쿼리로 screen_id 참조) =====
INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layer_name, layout_data, created_at, updated_at)
VALUES (
(SELECT screen_id FROM screen_definitions WHERE screen_code = 'ILSHIN_ORDER_MNG'),
'ILSHIN', 1, '기본 레이어',
'{
"version": "2.0",
"components": [
{
"id": "split_order",
"url": "@/lib/registry/components/v2-split-panel-layout",
"position": {"x": 0, "y": 0},
"size": {"width": 1920, "height": 850},
"displayOrder": 0,
"overrides": {
"label": "수주관리",
"splitRatio": 35,
"resizable": true,
"autoLoad": true,
"syncSelection": true,
"leftPanel": {
"title": "수주 목록",
"displayMode": "table",
"tableName": "order_master",
"showSearch": true,
"showAdd": true,
"showDelete": true,
"columns": [
{"name": "order_no", "label": "수주번호", "width": 120, "sortable": true},
{"name": "customer_id", "label": "거래처", "width": 150, "isEntityJoin": true, "joinInfo": {"sourceTable": "order_master", "sourceColumn": "customer_id", "referenceTable": "customer_info", "joinAlias": "customer_name"}},
{"name": "order_date", "label": "수주일자", "width": 100},
{"name": "status", "label": "상태", "width": 80},
{"name": "total_amount", "label": "총금액", "width": 120}
],
"addButton": {"enabled": true, "mode": "auto"},
"deleteButton": {"enabled": true, "confirmMessage": "선택한 수주를 삭제하시겠습니까?"}
},
"rightPanel": {
"title": "수주 상세",
"displayMode": "table",
"tableName": "order_detail",
"relation": {
"type": "detail",
"leftColumn": "id",
"rightColumn": "order_master_id",
"foreignKey": "order_master_id"
},
"columns": [
{"name": "item_id", "label": "품목", "width": 150, "isEntityJoin": true, "joinInfo": {"sourceTable": "order_detail", "sourceColumn": "item_id", "referenceTable": "item_info", "joinAlias": "item_name"}},
{"name": "quantity", "label": "수량", "width": 80, "editable": true},
{"name": "unit_price", "label": "단가", "width": 100, "editable": true},
{"name": "amount", "label": "금액", "width": 100},
{"name": "notes", "label": "비고", "width": 200, "editable": true}
],
"addButton": {"enabled": true, "mode": "auto"},
"editButton": {"enabled": true, "mode": "auto"},
"deleteButton": {"enabled": true, "confirmMessage": "삭제하시겠습니까?"}
}
}
}
],
"gridSettings": {"columns": 12, "gap": 16, "padding": 16},
"screenResolution": {"width": 1920, "height": 1080}
}'::jsonb,
now(), now()
)
ON CONFLICT (screen_id, company_code, layer_id) DO UPDATE SET layout_data = EXCLUDED.layout_data, updated_at = now();
-- ===== Step 7: menu_info (objid에 순서값 더해서 PK 충돌 방지) =====
INSERT INTO menu_info (
objid, menu_type, parent_obj_id, menu_name_kor, menu_name_eng,
seq, menu_url, menu_desc, writer, regdate, status, company_code, screen_code
)
VALUES (
extract(epoch from now())::bigint * 1000 + 1, 0, {부모_메뉴_objid},
'수주관리', 'Order Management',
1, '/screen/ILSHIN_ORDER_MNG', '수주 마스터-디테일 관리',
'admin', now(), 'active', 'ILSHIN', 'ILSHIN_ORDER_MNG'
);
16. 컴포넌트 빠른 참조표
| 요구사항 | 컴포넌트 url | 핵심 overrides |
|---|---|---|
| 데이터 테이블 | v2-table-list | tableName, columns, pagination |
| 검색 바 | v2-table-search-widget | autoSelectFirstTable |
| 좌우 분할 | v2-split-panel-layout | leftPanel, rightPanel, relation, splitRatio |
| 그룹화 테이블 | v2-table-grouped | groupConfig.groupByColumn, summary |
| 간트차트 | v2-timeline-scheduler | fieldMapping, resourceTable |
| 피벗 분석 | v2-pivot-grid | fields(area, summaryType) |
| 카드 뷰 | v2-card-display | columnMapping, cardsPerRow |
| 액션 버튼 | v2-button-primary | text, actionType, webTypeConfig.dataflowConfig |
| 텍스트 입력 | v2-input | inputType, tableName, columnName |
| 선택 | v2-select | mode, source |
| 날짜 | v2-date | dateType |
| 자동 채번 | v2-numbering-rule | rule |
| BOM 트리 | v2-bom-tree | detailTable, foreignKey, parentKey |
| BOM 편집 | v2-bom-item-editor | detailTable, sourceTable, itemCodeField |
| 결재 스테퍼 | v2-approval-step | targetTable, displayMode |
| 파일 업로드 | v2-file-upload | multiple, accept, maxSize |
| 상태별 건수 | v2-status-count | tableName, statusColumn, items |
| 집계 카드 | v2-aggregation-widget | tableName, items |
| 반복 데이터 관리 | v2-repeater | renderMode, mainTableName, foreignKeyColumn |
| 반복 렌더링 | v2-repeat-container | dataSourceType, layout, gridColumns |
| 그룹 컨테이너 (테두리) | v2-section-card | title, collapsible, borderStyle |
| 그룹 컨테이너 (배경색) | v2-section-paper | backgroundColor, shadow, padding |
| 캔버스 분할선 | v2-split-line | resizable, lineColor, lineWidth |
| 카테고리 관리 | v2-category-manager | tableName, columnName, menuObjid |
| 미디어 | v2-media | mediaType, multiple, maxSize |
| 위치 교환 | v2-location-swap-selector | dataSource, departureField, destinationField |
| 창고 랙 | v2-rack-structure | codePattern, namePattern, maxRows |
| 공정 작업기준 | v2-process-work-standard | dataSource.itemTable, dataSource.routingDetailTable |
| 품목 라우팅 | v2-item-routing | dataSource.itemTable, dataSource.routingDetailTable |