# POP 작업진행 관리 설계서 > 작성일: 2026-03-13 > 목적: POP 시스템에서 작업지시 기반으로 라우팅/작업기준정보를 조회하고, 공정별 작업 진행 상태를 관리하는 구조 설계 --- ## 1. 핵심 설계 원칙 **작업지시에 라우팅ID, 작업기준정보ID 등을 별도 컬럼으로 넣지 않는다.** - 작업지시(`work_instruction`)에는 `item_id`(품목 ID)만 있으면 충분 - 품목 → 라우팅 → 작업기준정보는 마스터 데이터 체인으로 조회 - 작업 진행 상태만 별도 테이블에서 관리 --- ## 2. 기존 테이블 구조 (마스터 데이터) ### 2-1. ER 다이어그램 > GitHub / VSCode Mermaid 플러그인에서 렌더링됩니다. ```mermaid erDiagram %% ========== 마스터 데이터 (변경 없음) ========== item_info { varchar id PK "UUID" varchar item_number "품번" varchar item_name "품명" varchar company_code "회사코드" } item_routing_version { varchar id PK "UUID" varchar item_code "품번 (= item_info.item_number)" varchar version_name "버전명" boolean is_default "기본버전 여부" varchar company_code "회사코드" } item_routing_detail { varchar id PK "UUID" varchar routing_version_id FK "→ item_routing_version.id" varchar seq_no "공정순서 10,20,30..." varchar process_code FK "→ process_mng.process_code" varchar is_required "필수/선택" varchar is_fixed_order "고정/선택" varchar standard_time "표준시간(분)" varchar company_code "회사코드" } process_mng { varchar id PK "UUID" varchar process_code "공정코드" varchar process_name "공정명" varchar process_type "공정유형" varchar company_code "회사코드" } process_work_item { varchar id PK "UUID" varchar routing_detail_id FK "→ item_routing_detail.id" varchar work_phase "PRE / IN / POST" varchar title "작업항목명" varchar is_required "Y/N" int sort_order "정렬순서" varchar company_code "회사코드" } process_work_item_detail { varchar id PK "UUID" varchar work_item_id FK "→ process_work_item.id" varchar detail_type "check/inspect/input/procedure/info" varchar content "내용" varchar input_type "입력타입" varchar inspection_code "검사코드" varchar unit "단위" varchar lower_limit "하한값" varchar upper_limit "상한값" varchar company_code "회사코드" } %% ========== 트랜잭션 데이터 ========== work_instruction { varchar id PK "UUID" varchar work_instruction_no "작업지시번호" varchar item_id FK "→ item_info.id ★핵심★" varchar status "waiting/in_progress/completed/cancelled" varchar qty "지시수량" varchar completed_qty "완성수량" varchar worker "작업자" varchar company_code "회사코드" } work_order_process { varchar id PK "UUID" varchar wo_id FK "→ work_instruction.id" varchar routing_detail_id FK "→ item_routing_detail.id ★추가★" varchar seq_no "공정순서" varchar process_code "공정코드" varchar process_name "공정명" varchar status "waiting/in_progress/completed/skipped" varchar plan_qty "계획수량" varchar good_qty "양품수량" varchar defect_qty "불량수량" timestamp started_at "시작시간" timestamp completed_at "완료시간" varchar company_code "회사코드" } work_order_work_item { varchar id PK "UUID ★신규★" varchar company_code "회사코드" varchar work_order_process_id FK "→ work_order_process.id" varchar work_item_id FK "→ process_work_item.id" varchar work_phase "PRE/IN/POST" varchar status "pending/completed/skipped/failed" varchar completed_by "완료자" timestamp completed_at "완료시간" } work_order_work_item_result { varchar id PK "UUID ★신규★" varchar company_code "회사코드" varchar work_order_work_item_id FK "→ work_order_work_item.id" varchar work_item_detail_id FK "→ process_work_item_detail.id" varchar detail_type "check/inspect/input/procedure" varchar result_value "결과값" varchar is_passed "Y/N/null" varchar recorded_by "기록자" timestamp recorded_at "기록시간" } %% ========== 관계 ========== %% 마스터 체인: 품목 → 라우팅 → 작업기준정보 item_info ||--o{ item_routing_version : "item_number = item_code" item_routing_version ||--o{ item_routing_detail : "id = routing_version_id" item_routing_detail }o--|| process_mng : "process_code" item_routing_detail ||--o{ process_work_item : "id = routing_detail_id" process_work_item ||--o{ process_work_item_detail : "id = work_item_id" %% 트랜잭션: 작업지시 → 공정진행 → 작업기준정보 진행 work_instruction }o--|| item_info : "item_id = id" work_instruction ||--o{ work_order_process : "id = wo_id" work_order_process }o--|| item_routing_detail : "routing_detail_id = id" work_order_process ||--o{ work_order_work_item : "id = work_order_process_id" work_order_work_item }o--|| process_work_item : "work_item_id = id" work_order_work_item ||--o{ work_order_work_item_result : "id = work_order_work_item_id" work_order_work_item_result }o--|| process_work_item_detail : "work_item_detail_id = id" ``` ### 2-1-1. 관계 요약 (텍스트) ``` [마스터 데이터 체인 - 조회용, 변경 없음] item_info ─── 1:N ───→ item_routing_version ─── 1:N ───→ item_routing_detail (품목) item_number (라우팅 버전) routing_ (공정별 상세) = item_code version_id │ process_mng ◄───┘ process_code (공정 마스터) │ ├── 1:N ───→ process_work_item ─── 1:N ───→ process_work_item_detail │ (작업기준정보) (작업기준정보 상세) │ routing_detail_id work_item_id │ [트랜잭션 데이터 - 상태 관리] │ │ work_instruction ─── 1:N ───→ work_order_process ─┘ routing_detail_id (★추가★) (작업지시) wo_id (공정별 진행) item_id → item_info │ ├── 1:N ───→ work_order_work_item ─── 1:N ───→ work_order_work_item_result │ (작업기준정보 진행) (상세 결과값) │ work_order_process_id work_order_work_item_id │ work_item_id → process_work_item work_item_detail_id → process_work_item_detail │ ★신규 테이블★ ★신규 테이블★ ``` ### 2-2. 마스터 테이블 상세 #### item_info (품목 마스터) | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | item_number | 품번 | item_routing_version.item_code와 매칭 | | item_name | 품명 | | | company_code | 회사코드 | 멀티테넌시 | #### item_routing_version (라우팅 버전) | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | item_code | 품번 | item_info.item_number와 매칭 | | version_name | 버전명 | 예: "기본 라우팅", "버전2" | | is_default | 기본 버전 여부 | true/false, 기본 버전을 사용 | | company_code | 회사코드 | | #### item_routing_detail (라우팅 상세 - 공정별) | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | routing_version_id | FK → item_routing_version.id | | | seq_no | 공정 순서 | 10, 20, 30... | | process_code | 공정코드 | FK → process_mng.process_code | | is_required | 필수/선택 | "필수" / "선택" | | is_fixed_order | 순서고정 여부 | "고정" / "선택" | | work_type | 작업유형 | | | standard_time | 표준시간(분) | | | outsource_supplier | 외주업체 | | | company_code | 회사코드 | | #### process_work_item (작업기준정보) | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | routing_detail_id | FK → item_routing_detail.id | | | work_phase | 작업단계 | PRE(작업전) / IN(작업중) / POST(작업후) | | title | 작업항목명 | 예: "장비 체크", "소재 준비" | | is_required | 필수여부 | Y/N | | sort_order | 정렬순서 | | | description | 설명 | | | company_code | 회사코드 | | #### process_work_item_detail (작업기준정보 상세) | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | work_item_id | FK → process_work_item.id | | | detail_type | 상세유형 | check(체크) / inspect(검사) / input(입력) / procedure(절차) / info(정보) | | content | 내용 | 예: "소음검사", "치수검사" | | input_type | 입력타입 | select, text 등 | | inspection_code | 검사코드 | | | inspection_method | 검사방법 | | | unit | 단위 | | | lower_limit | 하한값 | | | upper_limit | 상한값 | | | is_required | 필수여부 | Y/N | | sort_order | 정렬순서 | | | company_code | 회사코드 | | --- ## 3. 작업 진행 테이블 (트랜잭션 데이터) ### 3-1. work_instruction (작업지시) - 기존 테이블 | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | work_instruction_no | 작업지시번호 | 예: WO-2026-001 | | **item_id** | **FK → item_info.id** | **이것만으로 라우팅/작업기준정보 전부 조회 가능** | | status | 작업지시 상태 | waiting / in_progress / completed / cancelled | | qty | 지시수량 | | | completed_qty | 완성수량 | | | work_team | 작업팀 | | | worker | 작업자 | | | equipment_id | 설비 | | | start_date | 시작일 | | | end_date | 종료일 | | | remark | 비고 | | | company_code | 회사코드 | | > **routing 컬럼**: 현재 존재하지만 사용하지 않음 (null). 라우팅 버전을 지정하고 싶으면 이 컬럼에 `item_routing_version.id`를 넣어 특정 버전을 지정할 수 있음. 없으면 `is_default=true` 버전 자동 사용. ### 3-2. work_order_process (공정별 진행) - 기존 테이블, 변경 필요 작업지시가 생성될 때, 해당 품목의 라우팅 공정을 복사해서 이 테이블에 INSERT. | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | | | wo_id | FK → work_instruction.id | 작업지시 참조 | | **routing_detail_id** | **FK → item_routing_detail.id** | **추가 필요 - 라우팅 상세 참조** | | seq_no | 공정 순서 | 라우팅에서 복사 | | process_code | 공정코드 | 라우팅에서 복사 | | process_name | 공정명 | 라우팅에서 복사 (비정규화, 조회 편의) | | is_required | 필수여부 | 라우팅에서 복사 | | is_fixed_order | 순서고정 | 라우팅에서 복사 | | standard_time | 표준시간 | 라우팅에서 복사 | | **status** | **공정 상태** | **waiting / in_progress / completed / skipped** | | plan_qty | 계획수량 | | | input_qty | 투입수량 | | | good_qty | 양품수량 | | | defect_qty | 불량수량 | | | equipment_code | 사용설비 | | | accepted_by | 접수자 | | | accepted_at | 접수시간 | | | started_at | 시작시간 | | | completed_at | 완료시간 | | | remark | 비고 | | | company_code | 회사코드 | | ### 3-3. work_order_work_item (작업기준정보별 진행) - 신규 테이블 POP에서 작업자가 각 작업기준정보 항목을 체크/입력할 때 사용. | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | gen_random_uuid() | | company_code | 회사코드 | 멀티테넌시 | | work_order_process_id | FK → work_order_process.id | 어떤 작업지시의 어떤 공정인지 | | work_item_id | FK → process_work_item.id | 어떤 작업기준정보인지 | | work_phase | 작업단계 | PRE / IN / POST (마스터에서 복사) | | status | 완료상태 | pending / completed / skipped / failed | | completed_by | 완료자 | 작업자 ID | | completed_at | 완료시간 | | | created_date | 생성일 | | | updated_date | 수정일 | | | writer | 작성자 | | ### 3-4. work_order_work_item_result (작업기준정보 상세 결과) - 신규 테이블 작업기준정보의 상세 항목(체크, 검사, 입력 등)에 대한 실제 결과값 저장. | 컬럼 | 설명 | 비고 | |------|------|------| | id | PK (UUID) | gen_random_uuid() | | company_code | 회사코드 | 멀티테넌시 | | work_order_work_item_id | FK → work_order_work_item.id | | | work_item_detail_id | FK → process_work_item_detail.id | 어떤 상세항목인지 | | detail_type | 상세유형 | check / inspect / input / procedure (마스터에서 복사) | | result_value | 결과값 | 체크: "Y"/"N", 검사: 측정값, 입력: 입력값 | | is_passed | 합격여부 | Y / N / null(해당없음) | | remark | 비고 | 불합격 사유 등 | | recorded_by | 기록자 | | | recorded_at | 기록시간 | | | created_date | 생성일 | | | updated_date | 수정일 | | | writer | 작성자 | | --- ## 4. POP 데이터 플로우 ### 4-1. 작업지시 등록 시 (ERP 측) ``` [작업지시 생성] │ ├── 1. work_instruction INSERT (item_id, qty, status='waiting' 등) │ ├── 2. item_id → item_info.item_number 조회 │ ├── 3. item_number → item_routing_version 조회 (is_default=true 또는 지정 버전) │ ├── 4. routing_version_id → item_routing_detail 조회 (공정 목록) │ └── 5. 각 공정별로 work_order_process INSERT ├── wo_id = work_instruction.id ├── routing_detail_id = item_routing_detail.id ← 핵심! ├── seq_no, process_code, process_name 복사 ├── status = 'waiting' └── plan_qty = work_instruction.qty ``` ### 4-2. POP 작업 조회 시 ``` [POP 화면: 작업지시 선택] │ ├── 1. work_instruction 목록 조회 (status = 'waiting' or 'in_progress') │ ├── 2. 선택한 작업지시의 공정 목록 조회 │ SELECT wop.*, pm.process_name │ FROM work_order_process wop │ LEFT JOIN process_mng pm ON wop.process_code = pm.process_code │ WHERE wop.wo_id = {작업지시ID} │ ORDER BY CAST(wop.seq_no AS int) │ └── 3. 선택한 공정의 작업기준정보 조회 (마스터 데이터 참조) SELECT pwi.*, pwid.* FROM process_work_item pwi LEFT JOIN process_work_item_detail pwid ON pwi.id = pwid.work_item_id WHERE pwi.routing_detail_id = {work_order_process.routing_detail_id} ORDER BY pwi.work_phase, pwi.sort_order, pwid.sort_order ``` ### 4-3. POP 작업 실행 시 ``` [작업자가 공정 시작] │ ├── 1. work_order_process UPDATE │ SET status = 'in_progress', started_at = NOW(), accepted_by = {작업자} │ ├── 2. work_instruction UPDATE (첫 공정 시작 시) │ SET status = 'in_progress' │ ├── 3. 작업기준정보 항목별 체크/입력 시 │ ├── work_order_work_item UPSERT (항목별 상태) │ └── work_order_work_item_result UPSERT (상세 결과값) │ └── 4. 공정 완료 시 ├── work_order_process UPDATE │ SET status = 'completed', completed_at = NOW(), │ good_qty = {양품}, defect_qty = {불량} │ └── (모든 공정 완료 시) work_instruction UPDATE SET status = 'completed', completed_qty = {최종양품} ``` --- ## 5. 핵심 조회 쿼리 ### 5-1. 작업지시 → 전체 공정 + 작업기준정보 한방 조회 ```sql -- 작업지시의 공정별 진행 현황 + 작업기준정보 SELECT wi.work_instruction_no, wi.qty, wi.status as wi_status, ii.item_number, ii.item_name, wop.id as process_id, wop.seq_no, wop.process_code, wop.process_name, wop.status as process_status, wop.plan_qty, wop.good_qty, wop.defect_qty, wop.started_at, wop.completed_at, wop.routing_detail_id, -- 작업기준정보는 routing_detail_id로 마스터 조회 pwi.id as work_item_id, pwi.work_phase, pwi.title as work_item_title, pwi.is_required as work_item_required FROM work_instruction wi JOIN item_info ii ON wi.item_id = ii.id JOIN work_order_process wop ON wi.id = wop.wo_id LEFT JOIN process_work_item pwi ON wop.routing_detail_id = pwi.routing_detail_id WHERE wi.id = $1 AND wi.company_code = $2 ORDER BY CAST(wop.seq_no AS int), pwi.work_phase, pwi.sort_order; ``` ### 5-2. 특정 공정의 작업기준정보 + 진행 상태 조회 ```sql -- POP에서 특정 공정 선택 시: 마스터 + 진행 상태 조인 SELECT pwi.id as work_item_id, pwi.work_phase, pwi.title, pwi.is_required, pwid.id as detail_id, pwid.detail_type, pwid.content, pwid.input_type, pwid.inspection_code, pwid.inspection_method, pwid.unit, pwid.lower_limit, pwid.upper_limit, -- 진행 상태 wowi.status as item_status, wowi.completed_by, wowi.completed_at, -- 결과값 wowir.result_value, wowir.is_passed, wowir.remark as result_remark FROM process_work_item pwi LEFT JOIN process_work_item_detail pwid ON pwi.id = pwid.work_item_id LEFT JOIN work_order_work_item wowi ON wowi.work_item_id = pwi.id AND wowi.work_order_process_id = $1 -- work_order_process.id LEFT JOIN work_order_work_item_result wowir ON wowir.work_order_work_item_id = wowi.id AND wowir.work_item_detail_id = pwid.id WHERE pwi.routing_detail_id = $2 -- work_order_process.routing_detail_id ORDER BY CASE pwi.work_phase WHEN 'PRE' THEN 1 WHEN 'IN' THEN 2 WHEN 'POST' THEN 3 END, pwi.sort_order, pwid.sort_order; ``` --- ## 6. 변경사항 요약 ### 6-1. 기존 테이블 변경 | 테이블 | 변경내용 | |--------|---------| | work_order_process | `routing_detail_id VARCHAR(500)` 컬럼 추가 | ### 6-2. 신규 테이블 | 테이블 | 용도 | |--------|------| | work_order_work_item | 작업지시 공정별 작업기준정보 진행 상태 | | work_order_work_item_result | 작업기준정보 상세 항목의 실제 결과값 | ### 6-3. 건드리지 않는 것 | 테이블 | 이유 | |--------|------| | work_instruction | item_id만 있으면 충분. 라우팅/작업기준정보 ID 추가 불필요 | | item_routing_version | 마스터 데이터, 변경 없음 | | item_routing_detail | 마스터 데이터, 변경 없음 | | process_work_item | 마스터 데이터, 변경 없음 | | process_work_item_detail | 마스터 데이터, 변경 없음 | --- ## 7. DDL (마이그레이션 SQL) ```sql -- 1. work_order_process에 routing_detail_id 추가 ALTER TABLE work_order_process ADD COLUMN IF NOT EXISTS routing_detail_id VARCHAR(500); CREATE INDEX IF NOT EXISTS idx_wop_routing_detail_id ON work_order_process(routing_detail_id); -- 2. 작업기준정보별 진행 상태 테이블 CREATE TABLE IF NOT EXISTS work_order_work_item ( id VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, company_code VARCHAR(500) NOT NULL, work_order_process_id VARCHAR(500) NOT NULL, work_item_id VARCHAR(500) NOT NULL, work_phase VARCHAR(500), status VARCHAR(500) DEFAULT 'pending', completed_by VARCHAR(500), completed_at TIMESTAMP, created_date TIMESTAMP DEFAULT NOW(), updated_date TIMESTAMP DEFAULT NOW(), writer VARCHAR(500) ); CREATE INDEX idx_wowi_process_id ON work_order_work_item(work_order_process_id); CREATE INDEX idx_wowi_work_item_id ON work_order_work_item(work_item_id); CREATE INDEX idx_wowi_company_code ON work_order_work_item(company_code); -- 3. 작업기준정보 상세 결과 테이블 CREATE TABLE IF NOT EXISTS work_order_work_item_result ( id VARCHAR(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, company_code VARCHAR(500) NOT NULL, work_order_work_item_id VARCHAR(500) NOT NULL, work_item_detail_id VARCHAR(500) NOT NULL, detail_type VARCHAR(500), result_value VARCHAR(500), is_passed VARCHAR(500), remark TEXT, recorded_by VARCHAR(500), recorded_at TIMESTAMP DEFAULT NOW(), created_date TIMESTAMP DEFAULT NOW(), updated_date TIMESTAMP DEFAULT NOW(), writer VARCHAR(500) ); CREATE INDEX idx_wowir_work_order_work_item_id ON work_order_work_item_result(work_order_work_item_id); CREATE INDEX idx_wowir_detail_id ON work_order_work_item_result(work_item_detail_id); CREATE INDEX idx_wowir_company_code ON work_order_work_item_result(company_code); ``` --- ## 8. 상태값 정의 ### work_instruction.status (작업지시 상태) | 값 | 의미 | |----|------| | waiting | 대기 | | in_progress | 진행중 | | completed | 완료 | | cancelled | 취소 | ### work_order_process.status (공정 상태) | 값 | 의미 | |----|------| | waiting | 대기 (아직 시작 안 함) | | in_progress | 진행중 (작업자가 시작) | | completed | 완료 | | skipped | 건너뜀 (선택 공정인 경우) | ### work_order_work_item.status (작업기준정보 항목 상태) | 값 | 의미 | |----|------| | pending | 미완료 | | completed | 완료 | | skipped | 건너뜀 | | failed | 실패 (검사 불합격 등) | ### work_order_work_item_result.is_passed (검사 합격여부) | 값 | 의미 | |----|------| | Y | 합격 | | N | 불합격 | | null | 해당없음 (체크/입력 항목) | --- ## 9. 설계 의도 요약 1. **마스터와 트랜잭션 분리**: 라우팅/작업기준정보는 마스터(템플릿), 실제 진행은 트랜잭션 테이블에서 관리 2. **조회 경로**: `work_instruction.item_id` → `item_info.item_number` → `item_routing_version` → `item_routing_detail` → `process_work_item` → `process_work_item_detail` 3. **진행 경로**: `work_order_process.routing_detail_id`로 마스터 작업기준정보를 참조하되, 실제 진행/결과는 `work_order_work_item` + `work_order_work_item_result`에 저장 4. **중복 저장 최소화**: 작업지시에 공정/작업기준정보 ID를 넣지 않음. 품목만 있으면 전부 파생 조회 가능 5. **work_order_process**: 작업지시 생성 시 라우팅 공정을 복사하는 이유는 진행 중 수량/상태/시간 등 트랜잭션 데이터를 기록해야 하기 때문 (마스터가 변경되어도 이미 발행된 작업지시의 공정은 유지) --- ## 10. 주의사항 - `work_order_process`에 공정 정보를 복사(스냅샷)하는 이유: 마스터 라우팅이 나중에 변경되어도 이미 진행 중인 작업지시의 공정 구성은 영향받지 않아야 함 - `routing_detail_id`는 "이 공정이 어떤 마스터 라우팅에서 왔는지" 추적용. 작업기준정보 조회 키로 사용 - POP에서 작업기준정보를 표시할 때는 항상 마스터(`process_work_item`)를 조회하고, 결과만 트랜잭션 테이블에 저장 - 모든 테이블에 `company_code` 필수 (멀티테넌시)