# 입고등록 × 수입검사 연동 계획

> **작성일**: 2026-03-17
> **상태**: 구현 완료 (Phase 1 데이터 매핑 제외)
> **담당**: R&D실 (API) + 프론트엔드 개발자 (React)

---

## 1. 개요

### 1.1 목적

입고등록 시 선택한 품목에 맞는 수입검사 성적서(27종)를 자동 매칭하여 검사를 수행하고, 검사 결과를 입고 데이터에 반영한다.

### 1.2 현재 구현 상태

| 항목 | 상태 | 설명 |
|------|:----:|------|
| `DocumentService::resolve()` | ✅ | 품목 ID → 수입검사 템플릿 자동 매칭 |
| `ReceivingService::getItemsWithInspectionTemplate()` | ✅ | 입고 목록에서 `has_inspection_template` 플래그 반환 |
| React `checkInspectionTemplate()` | ✅ | 입고 상세 로드 시 API 호출 → `hasInspectionTemplate` 상태 설정 |
| React 수입검사 버튼 렌더링 | ✅ | 템플릿 존재 또는 검사결과 있으면 버튼 표시 |
| `ImportInspectionInputModal` | ✅ | 수입검사 입력 모달 (검사항목 동적 로드) |
| `InspectionModal` | ✅ | 수입검사 성적서 보기 모달 |
| MNG 중복 검증 | ✅ | 동일 category 내 같은 품목 중복 연결 방지 |
| 검사완료 → `inspection_completed` 상태 전이 | ✅ | 검사완료 시 자동 상태 변경 |
| 검사완료 → 재고 자동 생성 | ✅ | `inspection_completed` 상태에서 Stock/StockLot 자동 생성 |
| **품목 ↔ 템플릿 매핑 데이터** | ❌ | `linked_item_ids`에 품목 미연결 (27종 전부) |

### 1.3 핵심 포인트

> **MNG에서 수입검사 템플릿의 `linked_item_ids`에 품목을 연결하면, 입고 상세 화면에 수입검사 버튼이 자동으로 표시된다.** 추가 코드 변경 없이 데이터 매핑만으로 동작하는 구조.

---

## 2. 시스템 구조 (기존 인프라)

### 2.1 템플릿 ↔ 품목 매핑

```
MNG 문서양식관리
├─ document_templates (27종 수입검사 양식)
│   ├─ category: '수입검사'
│   ├─ linked_item_ids: [101, 102, 103]  ← 이 품목들이 이 검사서 사용
│   └─ sections → section_items (검사항목 N개, 동적)
│
API DocumentService::resolve()
├─ 입력: { category: 'incoming_inspection', item_id: 101 }
├─ 로직: linked_item_ids에 해당 item_id가 포함된 템플릿 검색
└─ 출력: { template: {...}, is_new: true/false }
```

### 2.2 입고 → 수입검사 → 재고 흐름 (구현 완료)

```
입고 등록 (receiving_pending)
  ↓
입고 상세 로드 (ReceivingDetail.tsx)
  ↓ checkInspectionTemplate(itemId)
  ↓
  ├─ 템플릿 존재 또는 검사결과 있음
  │   → [수입검사하기] + [수입검사성적서 보기] 버튼 표시
  │
  └─ 템플릿 없고 검사결과 없음
      → 버튼 미표시
  ↓
[수입검사하기] → ImportInspectionInputModal
  ↓ 검사항목 입력 → 검사완료 버튼
  ↓
saveInspectionData()
  ↓ Step 1: POST /v1/documents/upsert (검사 데이터 저장)
  ↓ Step 2: PUT /v1/receivings/{id} (status → inspection_completed)
  ↓
ReceivingService::update()
  ↓ inspection_completed 감지 → 재고 반영 대상
  ↓ StockService::increaseFromReceiving()
  ↓
Stock + StockLot 자동 생성 → 재고현황에 표시
```

### 2.3 상태 흐름 (확정)

```
receiving_pending ──수입검사완료──→ inspection_completed ──(재고 자동 생성)
(입고대기)                         (검사완료)

receiving_pending ──입고처리──→ completed ──(재고 자동 생성)
(입고대기)                      (입고완료)
```

> **핵심**: `inspection_completed`와 `completed` 두 상태 모두 재고 생성을 트리거한다.

### 2.4 재고 연동 조건 (`ReceivingService::update()`)

```php
$stockStatuses = ['completed', 'inspection_completed'];
$wasCompleted = in_array($oldStatus, $stockStatuses);
$isCompletingReceiving = in_array($newStatus, $stockStatuses) && !$wasCompleted;
```

| 상태 변경 | 재고 동작 |
|----------|----------|
| `receiving_pending` → `inspection_completed` | Stock/StockLot **생성** |
| `receiving_pending` → `completed` | Stock/StockLot **생성** |
| `inspection_completed` → `receiving_pending` | 재고 **차감** (전량) |
| `inspection_completed` → `inspection_completed` (수량 변경) | 재고 **조정** (차이분) |

> **view/edit 모드에서만 버튼 표시.** new 모드에서는 아직 품목이 저장되지 않았으므로 미표시.

---

## 3. 27종 검사서 ↔ 원자재 매핑 전략

### 3.1 매핑 방식: 1:N (한 템플릿 → 여러 품목)

```
수입검사 템플릿 A (SUS 판재)
  └─ linked_item_ids: [SUS304 3T, SUS304 2T, SUS316 3T, ...]

수입검사 템플릿 B (GI 도금강판)
  └─ linked_item_ids: [GI 0.5T, GI 0.8T, GI 1.0T, ...]

수입검사 템플릿 C (알루미늄)
  └─ linked_item_ids: [AL 40x40, AL 30x30, ...]

수입검사 템플릿 D (DC 모터)
  └─ linked_item_ids: [DC모터 24V, DC모터 12V, ...]
```

### 3.2 매핑 기준

| 기준 | 설명 |
|------|------|
| **재질 그룹** | SUS, GI, AL, 플라스틱 등 동일 재질은 같은 검사항목 |
| **형태 그룹** | 판재, 코일, 프로파일, 봉강 등 형태별 검사 기준 차이 |
| **부품 유형** | 모터, 전자부품, 볼트/너트, 접착제 등 |

### 3.3 매핑 설정 방법

MNG 문서양식관리에서 각 수입검사 템플릿 수정 시 **연결 품목**을 설정한다.

```
MNG > 문서 > 문서양식관리 > [수입검사 템플릿 선택] > 수정
  → "연결 품목" 영역에서 해당 검사서를 사용할 품목 선택
  → linked_item_ids에 자동 저장
```

> **중복 방지**: 동일 category(수입검사) 내에서 같은 품목을 두 템플릿에 중복 연결 불가 (MNG에서 자동 검증).

### 3.4 매핑 검증

매핑 완료 후 아래 체크:

```
✅ 모든 원자재(RM) 품목이 최소 1개 수입검사 템플릿에 연결됨
✅ 동일 품목이 2개 이상 수입검사 템플릿에 중복 연결되지 않음
✅ 템플릿 없는 품목은 입고 시 수입검사 건너뜀 (정상 동작)
```

---

## 4. 구현 계획

### Phase 1: 데이터 매핑 (MNG 작업, 코드 변경 없음)

MNG 문서양식관리에서 27종 수입검사 템플릿의 `linked_item_ids`에 원자재 품목 연결.

| 작업 | 담당 | 비고 |
|------|------|------|
| 27종 검사서 ↔ 원자재 매핑표 작성 | R&D실 | 재질/형태별 그룹핑 |
| MNG에서 linked_item_ids 설정 | R&D실 | 각 템플릿 수정 |
| 매핑 검증 (누락/중복 체크) | R&D실 | API로 검증 |

### Phase 2: API 보강 (백엔드) — ✅ 완료

| 작업 | 상태 | 설명 |
|------|:----:|------|
| `DocumentService::resolve()` | ✅ 완료 | 품목 → 템플릿 매칭 |
| `DocumentService::formatTemplateForReact()` | ✅ 완료 | 클로저 스코프 수정 (`$methodCodes`) |
| `ReceivingService::getItemsWithInspectionTemplate()` | ✅ 완료 | 입고 목록 `has_inspection_template` 플래그 |
| `Receiving::STATUSES` | ✅ 완료 | `inspection_completed` 상태 추가 |
| `ReceivingService::update()` | ✅ 완료 | `inspection_completed` 상태에서 재고 자동 생성 |
| `StoreReceivingRequest` | ✅ 완료 | `order_qty` required 검증, `inspection_completed` 허용 |
| 수입검사 결과 → Receiving options 반영 | ✅ 완료 | `saveInspectionData()`에서 자동 설정 |

### Phase 3: React UI 보강 (프론트엔드) — ✅ 완료

| 작업 | 상태 | 설명 |
|------|:----:|------|
| 수입검사하기 버튼 | ✅ 완료 | 템플릿 존재 또는 검사결과 있으면 표시 |
| ImportInspectionInputModal | ✅ 완료 | 검사항목 동적 로드 + 입력 |
| InspectionModal (성적서 보기) | ✅ 완료 | 저장된 검사 성적서 조회 |
| 검사완료 → `inspection_completed` 상태 전이 | ✅ 완료 | `saveInspectionData()` → status 자동 변경 |
| 입고 목록 삭제 기능 | ✅ 완료 | 체크박스 선택 → 삭제 버튼 표시 |
| 단위(unit) → API 전달 | ✅ 완료 | `transformFrontendToApi()`에서 `unit` 매핑 |

---

## 5. 관련 API 엔드포인트

### 5.1 구현 완료

```
GET /api/v1/documents/resolve
  params: { category: 'incoming_inspection', item_id: 101 }
  → 해당 품목의 수입검사 템플릿 + 기존 문서 반환

POST /api/v1/documents/upsert
  → 검사 데이터 저장 (sections/items/field_values)

PUT /api/v1/receivings/{id}
  body: { status: 'inspection_completed', inspection_status, inspection_date, inspection_result }
  → 상태 변경 + 재고 자동 생성 (inspection_completed 시)

GET /api/v1/items/{id}
  → 응답에 has_inspection_template 포함
```

> 별도 `PATCH /inspection-result` 엔드포인트는 불필요. `saveInspectionData()`가 `documents/upsert` + `receivings/{id}` PUT 2단계로 처리한다.

---

## 6. 입고등록 화면 구조 (구현 완료)

### 수입검사 버튼 (헤더 영역)

```
┌──────────────────────────────────────────────────────────────────┐
│  입고 상세                         [수입검사하기] [성적서 보기]    │
│                                    ↑ hasInspectionTemplate=true   │
├──────────────────────────────────────────────────────────────────┤
│  기본 정보 (Card)                                                │
│  ...                                                             │
├──────────────────────────────────────────────────────────────────┤
│  수입검사 정보 (Card)                                             │
│  검사일: (readOnly, options에서 자동)                              │
│  검사결과: (readOnly, options에서 자동)                            │
│  업체 제공 성적서: [파일 업로드]                                    │
└──────────────────────────────────────────────────────────────────┘
```

### 버튼 렌더링 조건 (ReceivingDetail.tsx)

```typescript
const showInspectionActions = hasInspectionTemplate
  || !!detail?.inspectionResult
  || !!detail?.inspectionDate;

const customHeaderActions =
  (isViewMode || isEditMode) && detail && showInspectionActions ? (
    <div className="flex gap-2">
      <Button onClick={handleInspection}>수입검사하기</Button>
      <Button onClick={handleViewInspectionReport}>수입검사성적서 보기</Button>
    </div>
  ) : undefined;
```

| 조건 | 결과 |
|------|------|
| new 모드 | 버튼 미표시 (품목 미저장) |
| view/edit + 템플릿 없고 검사결과 없음 | 버튼 미표시 |
| view/edit + `hasInspectionTemplate=true` | **두 버튼 모두 표시** |
| view/edit + 검사결과 또는 검사일 있음 | **두 버튼 모두 표시** (합격 후에도 유지) |

> **핵심**: 템플릿이 있거나, 이미 검사가 수행된 경우 버튼이 표시된다.

---

## 관련 문서

- [수입검사 성적서 연동 계획](incoming-inspection-document-integration-plan.md) — 기존 상세 설계
- [수입검사 템플릿 계획](incoming-inspection-templates-plan.md) — 템플릿 생성 계획
- [채번규칙](../../rules/numbering-rules.md) — 원자재 로트번호 채번
- [입고관리 분석](receiving-management-analysis-plan.md) — 입고 시스템 현황

---

**최종 업데이트**: 2026-03-20
