# POP 공정 타이머 구조 분석 및 개선안 작성일: 2026-05-08 대상: POP `ProcessWork` 화면 — 공정 시간 추적 구조 --- ## 1. 개요 POP에서 작업자가 누르는 "시작/정지/재개/종료" 버튼이 어떤 테이블의 어떤 컬럼을 갱신하는지, 현재 구조에서 작업자가 무엇을 인지하기 어려운지, 어떻게 정리해야 하는지를 한 문서에 정리한다. 핵심 결론 요약: - 시간은 **두 테이블**에 나뉘어 저장된다. - `work_order_process_result` — 공정 전체(1 공정 = 1 행) - `process_work_result` — 공정작업기준(공정 1개 안에 N 행, work_phase로 PRE/IN/POST 그룹화) - 백엔드는 두 종류의 타이머 엔드포인트를 제공한다 (`/timer`, `/group-timer`). - 그러나 **POP 화면에는 phase 타이머만 노출**되어 있고, 공정 전체 타이머 핸들러(`handleTimerAction`)는 정의만 되어 있고 어디에서도 호출되지 않는다. - 결과적으로 공정 전체의 `started_at`/`completed_at`은 phase 타이머·결과 저장 로직의 **부산물**로만 채워진다. 일시정지/실작업시간 컬럼은 운영 데이터상 거의 비어 있다. --- ## 2. 데이터 모델 ### 2.1 `work_order_process_result` — 공정 전체 - `wop_id` UNIQUE (1 공정 = 1 행) - 주요 컬럼 | 컬럼 | 의미 | 운영 상태 | |---|---|---| | `started_at` | 공정 시작 시각 | ✅ 사용 중 | | `completed_at` | 공정 완료 시각 | ✅ 사용 중 | | `paused_at` | 일시정지 시각 | ⚠️ 빈 값 | | `total_paused_time` | 누적 일시정지 시간(초) | ⚠️ 빈 값 | | `actual_work_time` | 실작업시간(초) | ⚠️ 빈 값 | | `accepted_at` / `accepted_by` | 작업 수락 | 별도 흐름 | | `input_qty` / `good_qty` / `defect_qty` / `concession_qty` | 수량 | ✅ saveResult에서 갱신 | | `status` | waiting / in_progress / completed | ✅ | ### 2.2 `process_work_result` — 공정작업기준 - 공정 1개 안에 N 행 (작업 항목별) - `work_phase` 컬럼으로 PRE / IN / POST 3 단계로 그룹화 - 시간 컬럼은 **항목별이 아니라 phase 그룹 단위**로 동작 (phase 내 모든 row가 동일 시각 공유) | 컬럼 | 의미 | 운영 상태 | |---|---|---| | `recorded_at` | 항목 결과 기록 시각 | ✅ 사용 중 | | `started_at` | 항목 개별 시작 | ⚠️ 빈 값 (스키마만 존재) | | `duration_minutes` | 항목 소요 시간 | ⚠️ 빈 값 | | `group_started_at` | phase 그룹 시작 | ⚠️ 빈 값 | | `group_completed_at` | phase 그룹 완료 | ⚠️ 빈 값 | | `group_paused_at` / `group_total_paused_time` | phase 일시정지 추적 | ⚠️ 빈 값 | | `result_value` / `is_passed` | 측정값 / 판정 | ✅ | ### 2.3 시간 추적 단위 시각화 ``` work_order_process_result (1행) └─ started_at / completed_at / paused_at / total_paused_time / actual_work_time │ └── process_work_result (N행, work_phase 그룹화) ├─ phase=PRE ─┬─ 항목1 │ ├─ 항목2 ← 같은 group_started_at / group_completed_at 공유 │ └─ 항목3 ├─ phase=IN ─┬─ 항목4 │ └─ 항목5 ← 별개의 group_* 공유 └─ phase=POST ─┬─ 항목6 └─ 항목7 ``` --- ## 3. 백엔드 시간 저장 흐름 ### 3.1 `POST /pop/production/timer` — 공정 전체 타이머 (`controlTimer`) `backend-node/src/controllers/popProductionController.ts:885` | action | 동작 | |---|---| | `start` | `wop_result.started_at = NOW()` (NULL일 때만), `status = 'in_progress'` | | `pause` | `wop_result.paused_at = NOW()` | | `resume` | `total_paused_time += (NOW - paused_at)`, `paused_at = NULL` | | `complete` | `completed_at = NOW()`, `actual_work_time` = phase 시간 합산, `status = 'completed'`, 수량 갱신 | ### 3.2 `POST /pop/production/group-timer` — Phase 그룹 타이머 (`controlGroupTimer`) `backend-node/src/controllers/popProductionController.ts:1026` | action | 동작 | |---|---| | `start` | `process_work_result.group_started_at = NOW()` (phase 내 모든 row), 동시에 부모 `wop_result.started_at`도 NULL이면 NOW로 | | `pause` | `group_paused_at = NOW()` | | `resume` | `group_total_paused_time += (NOW - group_paused_at)` | | `complete` | `group_completed_at = NOW()`, paused 정산, **`wop_result.actual_work_time`을 phase별 MAX 시간 SUM으로 재계산** | → phase 타이머가 **공정 전체 타이머에도 영향을 주는 의존성**이 있다. ### 3.3 결과 저장 / 확정 (`saveResult`, `confirmResult`) - 결과 저장/확정 시 `wop_result.completed_at`, `status`, 수량 등을 추가 갱신. - 즉 공정 전체 종료 시각은 (1) `controlTimer` complete, (2) `controlGroupTimer` complete + saveResult 흐름, (3) confirmResult 흐름 등 **여러 경로에서 갱신**될 수 있다. --- ## 4. 프론트(POP) UI 현황 ### 4.1 `ProcessWork.tsx` 함수 정의 | 함수 | 위치 | UI 노출 | |---|---|---| | `handleTimerAction` (공정 전체) | line 909 | ❌ **호출하는 UI 없음** | | `handlePhaseTimerAction` (phase) | line 965 | ✅ Group Summary Bar에 노출 (line 1745~) | ### 4.2 작업자가 보는 화면 - 우측 상단 Group Summary Bar에 **선택된 phase의 타이머만** 표시 - 상태별 버튼: 시작 / 정지 / 종료 / 재개 / 완료 배지 - 경과 시간(`elapsedSec`)을 모노스페이스 폰트로 표시 - 공정 전체의 시작/종료/실작업시간은 **화면에 직접 표시되지 않음** ### 4.3 작업자 인지의 한계 | 항목 | 작업자 시점 | |---|---| | "공정 자체가 언제 시작됐나" | 알 수 없음 (UI 없음) | | "공정 전체 진행 상태가 어디까지 왔나" | phase별 타이머만으로 추정해야 함 | | 일시정지 / 실작업시간 | 표시되지 않음, 운영상 채워지지도 않음 | | 종료 버튼이 phase 종료인지 공정 종료인지 | "종료" 버튼이 phase 종료를 의미하지만 라벨상 혼동 가능 | --- ## 5. 운영 데이터 실태 (개발 DB) | 테이블/컬럼 | 채움 상태 | |---|---| | `wop_result.started_at` / `completed_at` | ✅ 채워짐 (단, controlTimer가 아니라 controlGroupTimer/saveResult 부산물) | | `wop_result.paused_at`, `total_paused_time`, `actual_work_time` | ❌ 거의 빈 값 | | `process_work_result.group_started_at`, `group_completed_at`, `group_*` | ❌ 빈 값 — phase 타이머 사용 흔적 없음 | | `process_work_result.recorded_at` | ✅ 결과 입력 시 기록 | **시사점**: 현장 작업자는 phase 타이머 버튼을 거의 쓰지 않거나, 자동 시작 트리거(입력 영역 상호작용)만 발동시키고 명시적으로 누르지 않는 것으로 추정. --- ## 6. 데이터 모델 정리안 ### 6.1 현재 문제점 1. **공정 전체 시간이 다중 경로로 갱신** — `controlTimer.complete`, `controlGroupTimer.start`, `saveResult`, `confirmResult` 4 경로. 어느 경로가 권위 있는지 불명확. 2. **항목 단위 시간 컬럼은 사실상 죽은 스키마** — `process_work_result.started_at`, `duration_minutes`는 한 번도 채워진 적 없음. 3. **컬럼 타입이 모두 `character varying(500)`** — 시간/수치 컬럼이 문자열로 저장됨. 인덱스 효율, 형 변환 비용, 정합성 모두 약화. 4. **phase 타이머 = 공정 타이머**에 가까움 — phase별 시간을 SUM해서 공정 `actual_work_time`을 만든다. 작업자에게는 동일 개념을 두 번 말하는 것처럼 보일 수 있다. ### 6.2 정리 방향 (안) A. **공정 전체 타이머를 권위 있는 단일 진입점으로 격상** - `wop_result.started_at`/`completed_at`은 **`controlTimer`만이 갱신**하도록 단일화 - `controlGroupTimer.start`에서 부모 `wop_result.started_at`을 자동 갱신하던 로직 제거 - `saveResult`/`confirmResult`는 수량·status만 다루고 시각은 건드리지 않음 B. **죽은 항목별 시간 컬럼 제거 또는 명시적 사용** - 사용 의도 없으면 `process_work_result.started_at` / `duration_minutes` 제거 검토 - 사용한다면 항목 입력 시 자동 채움 로직 추가 C. **컬럼 타입 정리 (장기 과제)** - `started_at`/`completed_at`을 `timestamp with time zone`으로 마이그레이션 - `*_paused_time` / `actual_work_time` / `duration_minutes`를 `integer`(초)로 - 즉시 적용은 운영 데이터 마이그레이션 부담이 크므로 별도 기획서 필요 D. **phase 타이머의 책임 축소** - phase 타이머는 "각 단계의 소요 시간 분석용 지표"로 한정 - 공정 전체 진행/완료 판단은 `wop_result`만 본다 ### 6.3 정리 후 구조 (목표) ``` [공정 시작 버튼] ──────────────► controlTimer(start) └─ wop_result.started_at = NOW [작업자 항목별 입력] ──────► saveResult (수량/판정만, 시각은 wop_result 관여 없음) [phase별 타이머(선택)] ────────► controlGroupTimer └─ process_work_result.group_* 만 갱신 [공정 종료 버튼] ──────────────► controlTimer(complete) ├─ wop_result.completed_at = NOW ├─ actual_work_time = NOW - started_at - total_paused_time └─ status = 'completed' ``` --- ## 7. POP 작업자 인지 개선안 (UI) ### 7.1 화면 정보 위계 (제안) 상단 헤더에 **3 단계 정보**를 분명히 노출: ``` ┌─────────────────────────────────────────────────────────────────────┐ │ [공정명: ABC-001] [수량: 100/120] [공정 타이머: 01:23:45 ▶] │ ← 공정 전체 │ ─────────────────────────────────────────────────────────────────── │ │ PRE (00:05:12 ✓완료) IN (01:18:33 ▶진행) POST (--:--:-- 대기) │ ← phase 진행률 한눈에 │ ─────────────────────────────────────────────────────────────────── │ │ [선택된 phase 작업기준 입력 영역] │ └─────────────────────────────────────────────────────────────────────┘ ``` 핵심 요소: 1. **공정 전체 타이머 표시** — 현재 누락. `wop_result.started_at` 기준 경과시간을 헤더에 항상 표시. 2. **phase 진행률 한눈 표시** — 모든 phase의 상태(대기/진행/완료)와 누적 시간을 헤더 한 줄에 압축 표시. 3. **버튼 라벨 명확화** — phase 영역의 "종료" 버튼은 "단계 완료"로, 공정 전체 종료는 별도 큰 버튼("공정 종료")으로 분리. 4. **일시정지 가시화** — paused 상태일 때 화면에 사유 입력(또는 표시) 영역 노출. 누적 정지시간을 부각. ### 7.2 상태 전이의 명시화 | 작업자 행동 | 화면 변화 | |---|---| | 공정 시작 클릭 | 공정 타이머 시작, 첫 phase 자동 활성 | | phase 입력 시작 | 해당 phase 카드 강조 + 자동 시작(현재 동작 유지) | | phase 종료 | "단계 완료" 배지 부여, 다음 phase로 포커스 이동 | | 모든 phase 완료 | 공정 종료 버튼 강조 (그 전에는 disabled로 두어 오작동 방지 가능) | | 공정 종료 | 종료 시각·실작업시간·일시정지시간을 요약 카드로 표시 | ### 7.3 색상·타이포 가이드 (현 코드 기준 보강) - 공정 타이머: 짙은 파랑(brand) — 공정 단위 권위 - phase 타이머: 옅은 회색~파랑 — 보조 정보 - 정지 상태: 호박색(amber) 통일 (현재도 amber 사용 중) - 완료: 녹색 통일 (현재 유지) - 모든 시간 표시는 `tabular-nums monospace` (현재도 사용 중) 유지 --- ## 8. 권장 작업 순서 1. **(선결) 결정 필요** — phase 타이머 유지/제거/축소 중 어느 방향인지 확정. 2. **단기 (UI만)** — 공정 전체 타이머 표시 영역을 헤더에 추가하고 `handleTimerAction` 핸들러를 시작/종료 버튼에 연결. 작업자는 즉시 공정 진행 상태를 인지 가능. 3. **중기 (백엔드 정리)** — 시각 갱신 경로를 `controlTimer` 단일화. saveResult/confirmResult에서 시각 갱신 제거. 4. **중기 (UI 강화)** — phase 진행률 헤더 바, 일시정지 사유 가시화, 단계별 라벨 정비. 5. **장기 (스키마)** — 컬럼 타입 정리, 죽은 항목 컬럼 정리. --- ## 9. 영향 범위 - **공통 백엔드**: `popProductionController.ts` (모든 회사 자동 반영) - **회사별 프론트**: `frontend/app/(main)/COMPANY_*/pop/_components/production/ProcessWork.tsx` (7개 + COMPANY_30, 8개 회사) - 회사별 작업 시 명시적 지시 후 개별 Edit 진행 (CLAUDE.md 회사별 작업 범위 규칙 준수) - **DB**: 단기 작업은 스키마 변경 없음. 장기 작업 시 마이그레이션 별도 기획서 필요. --- ## 10. 미결 질문 (작업 착수 전 확인 사항) - phase(PRE/IN/POST) 타이머는 **운영상 의미 있는 데이터인가?** (현재 빈 값) — 의미 있다면 입력을 강제, 없다면 제거. - 일시정지 추적은 **어느 단위에서 필요한가?** (공정 단위만 충분한지, phase 단위까지 필요한지) - "공정 종료" 버튼을 누르는 권한은 **누구에게 줄 것인가?** (작업자 / 검수자 / 관리자) - 컬럼 타입 정리는 **본서버에도 적용할 것인가?** 적용 시 마이그레이션 일정 협의 필요.