# 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` 필수 (멀티테넌시)
