Files
wace_rps/docs/seonghyun/POP_화면_배포서버_마이그레이션_가이드.md
T
kjs 8be4159f17 Remove outdated documents related to the approval system and WACE system analysis
- Deleted the following files as they are no longer relevant to the current project structure:
  - 결재 시스템 구현 현황
  - 결재 시스템 v2 사용 가이드
  - WACE 시스템 문제점 분석 및 개선 계획
  - Agent Pipeline 한계점 분석
  - AI 기반 화면 자동 생성 시스템 설계서
  - WACE ERP Backend - 분석 문서 인덱스

These deletions help streamline the documentation and remove obsolete information, ensuring that only current and relevant resources are maintained.
2026-04-01 12:35:40 +09:00

23 KiB

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

-- 배포 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 실행)

PGPASSWORD='$DB_PASSWORD' psql -h 211.115.91.141 -p 11134 -U postgres -d plm

1-1. work_order_process

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

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 + 트리거

-- 로그 테이블
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

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

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 메타데이터 복사 (로컬 → 배포)

# 로컬 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로 안전하게 삽입 가능

# 로컬에서 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:
-- 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: 검증

-- 배포 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. 롤백 방법

-- 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 백업을 완료하세요.