POP 작업진행 관리 설계서
작성일: 2026-03-13
목적: POP 시스템에서 작업지시 기반으로 라우팅/작업기준정보를 조회하고, 공정별 작업 진행 상태를 관리하는 구조 설계
1. 핵심 설계 원칙
작업지시에 라우팅ID, 작업기준정보ID 등을 별도 컬럼으로 넣지 않는다.
- 작업지시(
work_instruction)에는 item_id(품목 ID)만 있으면 충분
- 품목 → 라우팅 → 작업기준정보는 마스터 데이터 체인으로 조회
- 작업 진행 상태만 별도 테이블에서 관리
2. 기존 테이블 구조 (마스터 데이터)
2-1. ER 다이어그램
GitHub / VSCode 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. 작업지시 → 전체 공정 + 작업기준정보 한방 조회
-- 작업지시의 공정별 진행 현황 + 작업기준정보
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. 특정 공정의 작업기준정보 + 진행 상태 조회
-- 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)
-- 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. 설계 의도 요약
- 마스터와 트랜잭션 분리: 라우팅/작업기준정보는 마스터(템플릿), 실제 진행은 트랜잭션 테이블에서 관리
- 조회 경로:
work_instruction.item_id → item_info.item_number → item_routing_version → item_routing_detail → process_work_item → process_work_item_detail
- 진행 경로:
work_order_process.routing_detail_id로 마스터 작업기준정보를 참조하되, 실제 진행/결과는 work_order_work_item + work_order_work_item_result에 저장
- 중복 저장 최소화: 작업지시에 공정/작업기준정보 ID를 넣지 않음. 품목만 있으면 전부 파생 조회 가능
- work_order_process: 작업지시 생성 시 라우팅 공정을 복사하는 이유는 진행 중 수량/상태/시간 등 트랜잭션 데이터를 기록해야 하기 때문 (마스터가 변경되어도 이미 발행된 작업지시의 공정은 유지)
10. 주의사항
work_order_process에 공정 정보를 복사(스냅샷)하는 이유: 마스터 라우팅이 나중에 변경되어도 이미 진행 중인 작업지시의 공정 구성은 영향받지 않아야 함
routing_detail_id는 "이 공정이 어떤 마스터 라우팅에서 왔는지" 추적용. 작업기준정보 조회 키로 사용
- POP에서 작업기준정보를 표시할 때는 항상 마스터(
process_work_item)를 조회하고, 결과만 트랜잭션 테이블에 저장
- 모든 테이블에
company_code 필수 (멀티테넌시)