Files
pipeline/docs/POP_화면_배포서버_마이그레이션_가이드.md
T
kjs ccb0c8df4c Add environment variable example and update .gitignore
- Created a new .env.example file to provide a template for environment variables, including database connection details, JWT settings, encryption keys, and external API keys.
- Updated .gitignore to include additional test output directories and archive files, ensuring that unnecessary files are not tracked by Git.
- Removed outdated approval test reports and scripts that are no longer needed, streamlining the project structure.

These changes improve the clarity of environment configuration and maintain a cleaner repository.
2026-04-01 12:12:15 +09:00

604 lines
23 KiB
Markdown

# POP 화면 배포서버 마이그레이션 가이드
> **작성일**: 2026-03-23
> **목적**: 로컬(탑씰 COMPANY_7) POP 화면 5종을 배포서버 COMPANY_21(테스트회사)로 복사
> **대상 화면**: 4173, 4479, 4480, 4576, 4577
> **주의**: DB 작업 전 반드시 백업 후 진행
---
## 0. 개념 정리 (먼저 읽기)
### PopDeployModal이란?
POP 관리 화면 내 내장된 **화면 배포 도구**입니다.
- **접근**: POP 디자이너 > 카테고리 트리 > 그룹 우클릭 또는 배포 버튼
- **하는 일**:
1. 선택한 화면들을 다른 회사 계정으로 복사 (`screen_definitions` 새 행 생성)
2. POP 레이아웃 JSON 복사 (`screen_layouts_pop`)
3. **layout_json 내 화면 ID 참조 자동 리매핑** (screenId, cartScreenId, sourceScreenId, targetScreenId)
4. 카테고리 그룹 구조 생성 (`screen_groups`, `screen_group_screens`)
5. numberingRuleId 자동 제거 (회사별 고유값이므로)
- **제약**: 같은 서버 안에서만 동작 (로컬 → 배포 서버 간 복사 불가)
- **권장 사용 시점**: 배포 DB COMPANY_7에 화면이 먼저 세팅된 후, 같은 배포 서버 내 테스트 계정으로 복사할 때
### 마이그레이션 전체 흐름
```
[로컬 DB / COMPANY_7] ──── SQL 직접 복사 ────→ [배포 DB / COMPANY_7]
PopDeployModal
(배포서버 내)
[배포 DB / COMPANY_21]
(테스트 환경)
```
---
## 1. 현황 요약
### 1-1. 환경 정보
| 구분 | 로컬 DB | 배포 DB |
|------|---------|---------|
| Host | 39.117.244.52:11132 | 211.115.91.141:11134 |
| Database | plm | plm |
| 소스 회사 | COMPANY_7 (탑씰) | - |
| 1차 타겟 | - | COMPANY_7 (탑씰, SQL 직접 삽입) |
| 2차 타겟 | - | COMPANY_21 (테스트회사, PopDeployModal) |
### 1-2. 복사 대상 화면
| 화면 ID | screen_code | screen_name | 역할 |
|---------|-------------|-------------|------|
| 4479 | COMPANY_7_179 | 홈 | POP 메인 홈 화면 |
| 4576 | COMPANY_7_194 | 입고메뉴 | 입고 카테고리 메뉴 |
| 4173 | COMPANY_7_169 | 구매입고 담기 | 구매입고 항목 선택/담기 |
| 4577 | COMPANY_7_195 | 구매입고 장바구니 | 장바구니 확인/입고 확정 |
| 4480 | COMPANY_7_180 | MES공정 | MES 생산실적 관리 |
### 1-3. POP 카테고리 구조 (screen_groups)
```
탑씰 (id:3134, code:TOPSSEAL)
├── 홈 #4479
├── 입고관리 (id:3216, code:INBOUND_MENU)
│ ├── 입고메뉴 #4576
│ └── 구매입고 (id:3221, code:PURCHASE RECEIPT)
│ ├── 구매입고 담기 #4173
│ └── 구매입고 장바구니 #4577
└── 생산실적 (id:3220, code:PRODUCTION RESULTS)
└── MES공정 #4480
```
### 1-4. 화면 간 상호참조 (layout_json 내부)
| 출발 화면 | 참조 방식 | 대상 화면 | JSON 키 |
|----------|----------|----------|---------|
| 4479 홈 | navigate | 4480 MES공정 | `screenId: "4480"` |
| 4479 홈 | navigate | 4576 입고메뉴 | `screenId: "4576"` |
| 4576 입고메뉴 | navigate | 4173 구매입고 | `screenId: "4173"` |
| 4173 구매입고 | cart-save | 4577 장바구니 | `cartScreenId: "4577"` |
| 4577 장바구니 | source | 4173 구매입고 | `sourceScreenId: 4173` (숫자) |
| 4577 장바구니 | navigate | 4173 구매입고 | `targetScreenId: "4173"` |
> PopDeployModal을 사용하면 이 참조들이 모두 자동 리매핑됩니다.
---
## 2. 배포 DB 현황 점검 결과
### 2-1. 테이블 누락 상태
| 테이블 | 로컬 | 배포 | MES/기능 의존도 |
|--------|------|------|----------------|
| work_order_process | O (37컬럼) | **없음** | MES공정 화면 전체 동작 불가 |
| process_work_result | O (35컬럼) | **없음** | MES 체크리스트 기능 불가 |
| work_order_process_log | O (12컬럼+트리거) | **없음** | 공정 변경 이력 로깅 불가 |
| cart_items | O (16컬럼) | **없음** | 구매입고 장바구니 전체 불가 |
### 2-2. 컬럼 누락 상태
| 테이블 | 누락 컬럼 | 영향 |
|--------|----------|------|
| work_instruction | `reason`, `completed_qty` | MES 완료수량 업데이트 실패 |
### 2-3. COMPANY_21 (테스트회사) 현황
| 항목 | 상태 |
|------|------|
| 회사 존재 | O (active) |
| 기존 POP 레이아웃 | 없음 (깨끗한 상태) |
| 로그인 가능 계정 | **0개 (계정 없음!)** |
| 기존 화면 수 | 23개 (일반 ERP 화면들) |
> **중요**: COMPANY_21에는 현재 등록된 사용자가 없습니다.
> 테스트 전에 사용자 계정을 먼저 생성해야 로그인 및 POP 테스트가 가능합니다.
### 2-4. COMPANY_7 (탑씰) 배포 서버 현황
- 기존 POP 레이아웃: `screen_id 4114` (테스트용 1개만 존재)
- 로그인 계정: `topseal_admin`, `topseal_admin2`, `topseal_user`, `test1`, `test2`
- 5개 화면(4173, 4479, 4480, 4576, 4577) 모두 배포 DB에 없음 → 안전하게 삽입 가능
---
## 3. 누락 테이블/컬럼 추가 방법
### 3-1. Vexplor DDL 시스템으로 가능한 작업
Vexplor에는 **관리자 DDL 실행 시스템**이 내장되어 있습니다.
- **접근**: 관리자 > 시스템관리 > 테이블관리 (`/admin/systemMng/tableMngList`)
- **가능한 작업**:
- 테이블 생성 (`POST /api/ddl/tables`)
- 컬럼 추가 (`POST /api/ddl/tables/:tableName/columns`)
- 생성 시 `table_type_columns` 메타데이터 자동 등록
- **권한**: 슈퍼 어드민 계정 (`company_code = '*'`)만 사용 가능
- **불가능한 것**: 트리거 함수 생성 (이건 psql 직접 실행 필요)
### 3-2. 작업 분류
| 작업 | 방법 | 비고 |
|------|------|------|
| work_order_process 생성 | **psql 직접 실행** | 컬럼 37개 + 인덱스 7개, UI 입력보다 SQL이 효율적 |
| process_work_result 생성 | **psql 직접 실행** | 컬럼 35개 |
| cart_items 생성 | **psql 직접 실행** | 컬럼 16개 + 인덱스 5개 |
| work_order_process_log 생성 | **psql 직접 실행** | 트리거 함수 포함 필수 |
| work_instruction 컬럼 추가 | DDL UI 또는 psql | 컬럼 2개, 어느 방법이든 가능 |
| table_type_columns 메타데이터 | psql COPY 명령 | 로컬에서 추출 후 배포에 삽입 |
> **결론**: DDL UI는 컬럼 추가(`work_instruction`) 정도에 활용하고,
> 테이블 생성은 모두 psql SQL 직접 실행이 현실적입니다.
---
## 4. 실행 절차 (단계별)
### STEP 0: 배포 DB 백업
```sql
-- 배포 DB에서 실행
CREATE TABLE backup_20260323_screen_definitions AS
SELECT * FROM screen_definitions WHERE company_code = 'COMPANY_7';
CREATE TABLE backup_20260323_screen_layouts_pop AS
SELECT * FROM screen_layouts_pop WHERE company_code = 'COMPANY_7';
CREATE TABLE backup_20260323_screen_groups AS
SELECT * FROM screen_groups WHERE company_code = 'COMPANY_7';
CREATE TABLE backup_20260323_screen_group_screens AS
SELECT * FROM screen_group_screens WHERE company_code = 'COMPANY_7';
```
---
### STEP 1: 누락 테이블 생성 (배포 DB에서 psql 실행)
```bash
PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm
```
#### 1-1. work_order_process
```sql
CREATE TABLE work_order_process (
id character varying(500) NOT NULL DEFAULT (gen_random_uuid())::text,
created_date timestamp without time zone DEFAULT now(),
updated_date timestamp without time zone DEFAULT now(),
writer character varying(255),
company_code character varying(255),
wo_id character varying(500),
seq_no character varying(255),
process_code character varying(255),
process_name character varying(255),
is_required character varying(255),
is_fixed_order character varying(255),
standard_time character varying(255),
status character varying(255),
accepted_by character varying(255),
accepted_at character varying(255),
started_at character varying(255),
completed_at character varying(255),
plan_qty character varying(255),
input_qty character varying(255),
good_qty character varying(255),
defect_qty character varying(255),
equipment_code character varying(255),
remark character varying(500),
paused_at character varying(500),
total_paused_time character varying(500) DEFAULT '0',
routing_detail_id character varying(500),
actual_work_time character varying(500) DEFAULT NULL::character varying,
completed_by character varying(500) DEFAULT NULL::character varying,
total_production_qty character varying(500),
defect_detail character varying(500),
result_note character varying(500),
result_status character varying(500) DEFAULT 'draft'::character varying,
attachments character varying(500),
parent_process_id character varying(500) DEFAULT NULL::character varying,
concession_qty character varying(500) DEFAULT '0'::character varying,
is_rework character varying(500) DEFAULT 'N'::character varying,
rework_source_id character varying(500) DEFAULT NULL::character varying,
CONSTRAINT work_order_process_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_wop_company ON work_order_process (company_code);
CREATE INDEX idx_wop_wo_id ON work_order_process (wo_id);
CREATE INDEX idx_wop_wo_id_seq_no ON work_order_process (wo_id, seq_no);
CREATE INDEX idx_wop_process ON work_order_process (company_code, process_code);
CREATE INDEX idx_wop_status ON work_order_process (company_code, process_code, status);
CREATE INDEX idx_wop_parent_process_id ON work_order_process (parent_process_id);
```
#### 1-2. process_work_result
```sql
CREATE TABLE process_work_result (
id character varying(500) NOT NULL DEFAULT (gen_random_uuid())::text,
created_date timestamp without time zone DEFAULT now(),
updated_date timestamp without time zone DEFAULT now(),
writer character varying(500) DEFAULT NULL::character varying,
company_code character varying(500),
work_order_process_id character varying(500),
source_work_item_id character varying(500),
source_detail_id character varying(500),
work_phase character varying(500),
item_title character varying(500),
item_sort_order character varying(500),
detail_content character varying(500),
detail_type character varying(500),
detail_sort_order character varying(500),
is_required character varying(500),
inspection_code character varying(500),
inspection_method character varying(500),
unit character varying(500),
lower_limit character varying(500),
upper_limit character varying(500),
input_type character varying(500),
lookup_target character varying(500),
display_fields character varying(500),
duration_minutes character varying(500),
status character varying(500),
result_value character varying(500),
is_passed character varying(500),
remark character varying(500),
recorded_by character varying(500),
recorded_at character varying(500),
started_at character varying(500) DEFAULT NULL::character varying,
group_started_at character varying(500) DEFAULT NULL::character varying,
group_paused_at character varying(500) DEFAULT NULL::character varying,
group_total_paused_time character varying(500) DEFAULT NULL::character varying,
group_completed_at character varying(500) DEFAULT NULL::character varying,
CONSTRAINT process_work_result_pkey PRIMARY KEY (id)
);
```
#### 1-3. work_order_process_log + 트리거
```sql
-- 로그 테이블
CREATE TABLE work_order_process_log (
log_id SERIAL PRIMARY KEY,
operation_type character varying(10) NOT NULL,
original_id character varying(100),
changed_column character varying(100),
old_value text,
new_value text,
changed_by character varying(50),
changed_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
ip_address character varying(50),
user_agent text,
full_row_before jsonb,
full_row_after jsonb
);
-- 트리거 함수 (로컬 DB에서 정확한 정의 먼저 추출)
-- 로컬에서: SELECT pg_get_functiondef(oid) FROM pg_proc WHERE proname = 'work_order_process_log_trigger_func';
-- 추출한 함수 정의를 아래에 붙여넣기
-- 트리거 등록 (함수 생성 후 실행)
CREATE TRIGGER work_order_process_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON work_order_process
FOR EACH ROW EXECUTE FUNCTION work_order_process_log_trigger_func();
```
#### 1-4. cart_items
```sql
CREATE TABLE cart_items (
id character varying(255) NOT NULL DEFAULT (gen_random_uuid())::text,
created_date timestamp without time zone DEFAULT now(),
updated_date timestamp without time zone DEFAULT now(),
company_code character varying(20),
cart_type character varying(255),
screen_id character varying(255),
user_id character varying(255),
source_table character varying(255),
row_key text,
row_data text,
quantity character varying(255),
unit character varying(255),
package_unit character varying(255),
package_entries text,
status character varying(255),
memo text,
CONSTRAINT cart_items_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_cart_items_company ON cart_items (company_code);
CREATE INDEX idx_cart_items_screen_user ON cart_items (screen_id, user_id);
CREATE INDEX idx_cart_items_type ON cart_items (cart_type);
CREATE INDEX idx_cart_items_status ON cart_items (status);
```
---
### STEP 2: 기존 테이블 컬럼 추가
#### 방법 A: DDL UI (관리자 화면)
1. 슈퍼 어드민 계정으로 배포서버 접속
2. 관리자 > 시스템관리 > 테이블관리
3. `work_instruction` 테이블 선택
4. 컬럼 추가 버튼 → `reason` (varchar 500) 추가
5. 컬럼 추가 버튼 → `completed_qty` (varchar 500, 기본값: '0') 추가
#### 방법 B: SQL 직접
```sql
ALTER TABLE work_instruction
ADD COLUMN IF NOT EXISTS reason character varying(500),
ADD COLUMN IF NOT EXISTS completed_qty character varying(500) DEFAULT '0'::character varying;
```
---
### STEP 3: table_type_columns 메타데이터 복사 (로컬 → 배포)
```bash
# 로컬 DB에서 추출
PGPASSWORD='ph0909!!' psql -h 39.117.244.52 -p 11132 -U postgres -d plm -c "
COPY (
SELECT * FROM table_type_columns
WHERE table_name IN ('work_order_process', 'cart_items', 'process_work_result')
) TO STDOUT WITH CSV HEADER" > /tmp/ttc_export.csv
# 배포 DB에 삽입 (충돌 시 무시)
PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm -c "
COPY table_type_columns FROM STDIN WITH CSV HEADER
ON CONFLICT DO NOTHING" < /tmp/ttc_export.csv
```
---
### STEP 4: 화면 5종 → 배포 COMPANY_7 복사 (SQL)
> 화면 ID 4173~4577은 배포 DB에 없으므로 동일 ID로 안전하게 삽입 가능
```bash
# 로컬에서 screen_definitions 추출
PGPASSWORD='ph0909!!' psql -h 39.117.244.52 -p 11132 -U postgres -d plm -c "
COPY (
SELECT * FROM screen_definitions
WHERE screen_id IN (4173, 4479, 4480, 4576, 4577)
) TO STDOUT WITH CSV HEADER" > /tmp/screen_def.csv
# 배포에 삽입
PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm -c "
COPY screen_definitions FROM STDIN WITH CSV HEADER
ON CONFLICT DO NOTHING" < /tmp/screen_def.csv
# screen_layouts_pop 추출 (layout_id 제외)
PGPASSWORD='ph0909!!' psql -h 39.117.244.52 -p 11132 -U postgres -d plm -c "
COPY (
SELECT screen_id, company_code, layout_data, created_at, updated_at, created_by, updated_by
FROM screen_layouts_pop
WHERE screen_id IN (4173, 4479, 4480, 4576, 4577)
) TO STDOUT WITH CSV HEADER" > /tmp/screen_layouts.csv
# 배포에 삽입
PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm -c "
COPY screen_layouts_pop (screen_id, company_code, layout_data, created_at, updated_at, created_by, updated_by)
FROM STDIN WITH CSV HEADER
ON CONFLICT (screen_id, company_code) DO NOTHING" < /tmp/screen_layouts.csv
# screen_groups 추출 (그룹 4개: 3134, 3216, 3220, 3221)
PGPASSWORD='ph0909!!' psql -h 39.117.244.52 -p 11132 -U postgres -d plm -c "
COPY (
SELECT * FROM screen_groups
WHERE id IN (3134, 3216, 3220, 3221)
) TO STDOUT WITH CSV HEADER" > /tmp/screen_groups.csv
# 배포에 삽입
PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm -c "
COPY screen_groups FROM STDIN WITH CSV HEADER
ON CONFLICT DO NOTHING" < /tmp/screen_groups.csv
# screen_group_screens 추출
PGPASSWORD='ph0909!!' psql -h 39.117.244.52 -p 11132 -U postgres -d plm -c "
COPY (
SELECT * FROM screen_group_screens
WHERE screen_id IN (4173, 4479, 4480, 4576, 4577)
) TO STDOUT WITH CSV HEADER" > /tmp/screen_group_screens.csv
# 배포에 삽입
PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm -c "
COPY screen_group_screens FROM STDIN WITH CSV HEADER
ON CONFLICT DO NOTHING" < /tmp/screen_group_screens.csv
# 시퀀스 동기화 (배포 DB에서 실행)
SELECT setval('screen_definitions_screen_id_seq',
GREATEST((SELECT MAX(screen_id) FROM screen_definitions),
(SELECT last_value FROM screen_definitions_screen_id_seq)));
SELECT setval('screen_layouts_pop_layout_id_seq',
GREATEST((SELECT MAX(layout_id) FROM screen_layouts_pop),
(SELECT last_value FROM screen_layouts_pop_layout_id_seq)));
SELECT setval('screen_groups_id_seq',
GREATEST((SELECT MAX(id) FROM screen_groups),
(SELECT last_value FROM screen_groups_id_seq)));
SELECT setval('screen_group_screens_id_seq',
GREATEST((SELECT MAX(id) FROM screen_group_screens),
(SELECT last_value FROM screen_group_screens_id_seq)));
```
---
### STEP 5: COMPANY_21 테스트 계정 사용자 생성
> COMPANY_21에 현재 등록된 사용자가 없습니다. 로그인하려면 계정이 필요합니다.
배포서버 관리자 화면(슈퍼 어드민)에서 COMPANY_21 소속 사용자를 추가합니다:
- 관리자 > 회사관리 > COMPANY_21 > 사용자 추가
- 또는 SQL:
```sql
-- user_info 테이블에 테스트 사용자 추가 (기존 패턴 참고)
-- 실제 password 해시는 기존 계정 방식과 동일하게 처리 필요
INSERT INTO user_info (user_id, user_name, company_code, user_type, status, password)
VALUES ('test21', '테스트계정', 'COMPANY_21', 'COMPANY_ADMIN', 'active', '-- 해시된 비밀번호 --');
```
---
### STEP 6: PopDeployModal로 COMPANY_7 → COMPANY_21 복사
1. 배포서버에서 `topseal_admin` 계정으로 로그인
2. POP 디자이너 > POP 관리 화면 > 카테고리 트리 접속
3. "탑씰" 그룹에서 배포 버튼 클릭
4. 대상 회사: **COMPANY_21 (테스트회사)** 선택
5. 5개 화면 포함 여부 확인 후 배포 실행
6. PopDeployModal이 COMPANY_21 전용 새 화면 ID 자동 부여 + 참조 자동 리매핑
---
### STEP 7: 검증
```sql
-- 배포 DB에서 실행 --
-- 7-1. 누락 테이블 생성 확인
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('work_order_process', 'cart_items', 'process_work_result', 'work_order_process_log');
-- 예상: 4건
-- 7-2. work_instruction 컬럼 확인
SELECT column_name FROM information_schema.columns
WHERE table_name = 'work_instruction' AND column_name IN ('reason', 'completed_qty');
-- 예상: 2건
-- 7-3. COMPANY_7 화면 삽입 확인
SELECT screen_id, screen_name, company_code FROM screen_definitions
WHERE screen_id IN (4173, 4479, 4480, 4576, 4577);
-- 예상: 5건 (COMPANY_7)
-- 7-4. COMPANY_21 화면 복사 확인 (PopDeployModal 후)
SELECT sd.screen_id, sd.screen_name, sd.company_code,
CASE WHEN slp.layout_id IS NOT NULL THEN 'Y' ELSE 'N' END as has_layout
FROM screen_definitions sd
LEFT JOIN screen_layouts_pop slp ON sd.screen_id = slp.screen_id
WHERE sd.company_code = 'COMPANY_21'
AND sd.screen_name IN ('', 'MES공정', '입고메뉴', '구매입고 담기', '구매입고 장바구니');
-- 예상: 5건 + has_layout = Y
-- 7-5. 화면 간 참조 무결성 (COMPANY_21 기준 새 ID로 리매핑됐는지 확인)
SELECT slp.screen_id,
layout_data::text LIKE '%screenId%' as has_nav_ref,
layout_data::text LIKE '%cartScreenId%' as has_cart_ref
FROM screen_layouts_pop slp
JOIN screen_definitions sd ON slp.screen_id = sd.screen_id
WHERE sd.company_code = 'COMPANY_21'
AND sd.screen_name IN ('', '입고메뉴', '구매입고 담기', '구매입고 장바구니');
-- 7-6. 시퀀스 정합성
SELECT 'screen_definitions' as tbl,
(SELECT MAX(screen_id) FROM screen_definitions) as max_id,
(SELECT last_value FROM screen_definitions_screen_id_seq) as seq_val,
CASE WHEN (SELECT last_value FROM screen_definitions_screen_id_seq)
>= (SELECT MAX(screen_id) FROM screen_definitions)
THEN 'OK' ELSE 'MISMATCH' END as status;
```
---
## 5. COMPANY_21 테스트 환경 수정 가능 여부
| 항목 | 수정 가능? | 방법 |
|------|----------|------|
| 회사명/정보 | O | 관리자 > 회사관리 |
| 사용자 추가/수정 | O | 관리자 > 사용자관리 |
| POP 화면 수정 | O | POP 디자이너에서 직접 편집 |
| 화면 삭제 후 재배포 | O | PopDeployModal 재실행 |
| 기존 ERP 화면 영향 없음 | O | POP 레이아웃 별도 테이블 관리 |
> COMPANY_21은 테스트 전용 계정이므로 자유롭게 수정/삭제 가능합니다.
> 기존 23개 ERP 화면(구매관리, 영업관리 등)은 POP과 무관하므로 건드리지 않아도 됩니다.
---
## 6. 요약: 실행 순서
| 순서 | 작업 | 방법 | 담당 |
|------|------|------|------|
| STEP 0 | 배포 DB 백업 | psql | DB 담당자 |
| STEP 1 | 누락 테이블 4개 생성 | psql SQL | DB 담당자 |
| STEP 2 | work_instruction 컬럼 추가 | DDL UI 또는 psql | DB 담당자 |
| STEP 3 | table_type_columns 메타데이터 복사 | psql COPY | DB 담당자 |
| STEP 4 | 화면 5종 COMPANY_7에 삽입 | psql COPY | DB 담당자 |
| STEP 5 | COMPANY_21 테스트 사용자 생성 | 관리자 UI 또는 SQL | 어드민 계정 |
| STEP 6 | PopDeployModal로 COMPANY_21 복사 | 배포서버 UI | 어드민 계정 |
| STEP 7 | 검증 쿼리 실행 | psql | DB 담당자 |
| STEP 8 | 브라우저 POP 화면 테스트 | 브라우저 | 테스터 |
---
## 7. 롤백 방법
```sql
-- COMPANY_21 POP 화면 삭제 (PopDeployModal로 생성된 것)
DELETE FROM screen_group_screens
WHERE screen_id IN (
SELECT screen_id FROM screen_definitions WHERE company_code = 'COMPANY_21'
AND screen_name IN ('', 'MES공정', '입고메뉴', '구매입고 담기', '구매입고 장바구니')
);
DELETE FROM screen_layouts_pop
WHERE screen_id IN (
SELECT screen_id FROM screen_definitions WHERE company_code = 'COMPANY_21'
AND screen_name IN ('', 'MES공정', '입고메뉴', '구매입고 담기', '구매입고 장바구니')
);
DELETE FROM screen_definitions
WHERE company_code = 'COMPANY_21'
AND screen_name IN ('', 'MES공정', '입고메뉴', '구매입고 담기', '구매입고 장바구니');
-- COMPANY_7 화면 삭제 (SQL로 삽입한 것)
DELETE FROM screen_group_screens WHERE screen_id IN (4173, 4479, 4480, 4576, 4577);
DELETE FROM screen_layouts_pop WHERE screen_id IN (4173, 4479, 4480, 4576, 4577);
DELETE FROM screen_definitions WHERE screen_id IN (4173, 4479, 4480, 4576, 4577);
DELETE FROM screen_groups WHERE id IN (3134, 3216, 3220, 3221)
AND id NOT IN (SELECT id FROM backup_20260323_screen_groups);
-- 생성한 테이블 제거
DROP TABLE IF EXISTS work_order_process_log;
DROP TABLE IF EXISTS process_work_result;
DROP TABLE IF EXISTS work_order_process;
DROP TABLE IF EXISTS cart_items;
-- 추가 컬럼 제거
ALTER TABLE work_instruction DROP COLUMN IF EXISTS reason;
ALTER TABLE work_instruction DROP COLUMN IF EXISTS completed_qty;
```
---
*이 문서는 로컬 DB와 배포 DB를 읽기 전용으로 점검한 결과를 바탕으로 작성되었습니다.*
*실제 실행 전 반드시 배포 DB 백업을 완료하세요.*