# QMS 문서 양식 연동 계획

> **작성일**: 2026-03-12
> **목적**: 수주서/출고증/납품확인서의 Mock 데이터를 실제 수주/출하 데이터로 교체하고 QMS 로트 추적 심사에 연동
> **기준 문서**: `docs/features/quality-management/README.md`, `docs/system/database/documents.md`
> **상태**: 🔄 진행중 (Phase 1-2 범위)

---

## 📍 현재 진행 상태

| 항목 | 내용 |
|------|------|
| **마지막 완료 작업** | 계획 수립 + 검증 분석 반영 |
| **다음 작업** | Phase 1.1 — 사전 조사 (DB 조회) → getOrderDetail() 보강 |
| **진행률** | 0/12 (0%) |
| **마지막 업데이트** | 2026-03-12 |

---

## 1. 개요

### 1.1 배경

QMS 로트 추적 심사에서 8종 서류를 확인할 때, **수주서/출고증/납품확인서** 3종이 Mock 데이터(하드코딩)로 렌더링되고 있다.
수입검사 성적서와 제품검사 성적서는 이미 `document_templates` 기반 실 데이터 연동이 완료되었다.

### 1.2 현재 상태

| 문서 | QMS 연동 | 데이터 | 양식 기반 |
|------|---------|--------|----------|
| 수입검사 성적서 | ✅ 완료 | ✅ 실 데이터 | ✅ document_templates |
| 제품검사 성적서 | ✅ 완료 | ✅ inspection_data + FQC | ✅ template 기반 |
| **수주서** | ⚠️ Mock | ❌ MOCK_* 하드코딩 (12개 상수) | ❌ 하드코딩 |
| **출고증** | ⚠️ Mock | ❌ MOCK_* 하드코딩 (12개 상수, 중복) | ❌ 하드코딩 |
| **납품확인서** | ⚠️ Mock | ❌ MOCK_* 하드코딩 (출고증과 동일) | ❌ 하드코딩 |

### 1.3 목표

```
┌─────────────────────────────────────────────────────────────────┐
│  🎯 핵심 목표 (Phase 1-2)                                       │
├─────────────────────────────────────────────────────────────────┤
│  1. Mock 데이터 제거 → 실제 수주/출하 데이터로 렌더링            │
│  2. QMS 로트 추적 심사에서 실 데이터 기반 서류 확인 가능          │
│  3. 수주 페이지 + 출하 페이지 문서 모달도 동시 실 데이터 전환     │
├─────────────────────────────────────────────────────────────────┤
│  ⏸️ 보류 (Phase 3-4 — Phase 1-2 완료 후 재검토)                 │
│  - mng.sam.kr/document-templates에서 양식 관리                  │
│  - 양식 기반 동적 렌더링 전환                                    │
│  - 사유: document_templates 스키마가 검사 성적서 전용 설계이므로  │
│    수주서/출고증에 적합한 양식 구조 재설계 필요                   │
└─────────────────────────────────────────────────────────────────┘
```

### 1.4 성공 기준

| 기준 | 설명 | 상태 |
|------|------|:----:|
| SC-1 | 수주 57(스크린), 59(철재)에서 수주서 실 데이터 렌더링 | ⏳ |
| SC-2 | 출하 13에서 출고증/납품확인서 실 데이터 렌더링 | ⏳ |
| SC-3 | QMS `/quality/qms`에서 수주서/출고증/납품확인서 모달 정상 표시 | ⏳ |
| SC-3b | 수주 페이지 OrderDocumentModal에서도 수주서 실 데이터 정상 표시 | ⏳ |
| SC-4 | ~~mng에서 수주서/출고증/납품확인서 양식 생성/편집 가능~~ | ⏸️ 보류 |
| SC-5 | ~~양식 기반 동적 렌더링으로 전환 완료~~ | ⏸️ 보류 |

### 1.5 변경 승인 정책

| 분류 | 예시 | 승인 |
|------|------|------|
| ✅ 즉시 가능 | Mock 데이터 제거, API 응답 필드 추가, 프론트 데이터 매핑 | 불필요 |
| ⚠️ 컨펌 필요 | API 엔드포인트 변경, 새 양식 카테고리 추가, DB 스키마 변경 | **필수** |
| 🔴 금지 | document_templates 테이블 구조 변경, 기존 수입검사 양식 로직 변경 | 별도 협의 |

### 1.6 준수 규칙
- `docs/dev/standards/api-rules.md` — Service-First, FormRequest
- `docs/dev/standards/quality-checklist.md` — 코드 품질
- `docs/dev/standards/git-conventions.md` — 커밋 컨벤션
- `docs/dev/standards/options-column-policy.md` — JSON options 정책

---

## 2. 대상 범위

### 2.1 테스트 데이터

| ID | 유형 | 로트번호 | URL |
|----|------|---------|-----|
| 수주 57 | 스크린 | KD-SS-260211-05 | `/sales/order-management-sales/57` |
| 수주 59 | 철재 | (확인필요) | `/sales/order-management-sales/59` |
| 출하 13 | 출고/납품 | - | `/outbound/shipments/13` |
| 입고 108 | 수입검사(참고) | - | `/material/receiving-management/108` |

### 2.2 Phase 목록

| Phase | 작업 | 상태 | 의존성 |
|-------|------|:----:|--------|
| **1** | 수주서 실 데이터 매핑 | ⏳ | 없음 |
| **2** | 출고증/납품확인서 실 데이터 매핑 | ⏳ | 없음 (Phase 1과 병렬 가능) |
| **3** | ~~mng 양식 템플릿 생성~~ | ⏸️ 보류 | Phase 1-2 완료 후 재검토 |
| **4** | ~~양식 기반 동적 렌더링 전환~~ | ⏸️ 보류 | Phase 3 완료 후 |
| **5** | 통합 검증 | ⏳ | Phase 1-2 완료 후 |

> **Phase 3-4 보류 사유**: `document_templates` 스키마는 검사 성적서용(고정 행 + EAV)으로 설계됨.
> 수주서/출고증은 동적 행 + 제품유형별 다른 컬럼 구조이므로 현재 스키마로는 부적합.
> Phase 1-2로 실 데이터 렌더링을 먼저 완료한 후, 양식 구조를 별도 설계하는 것이 합리적.

---

## 3. 작업 절차

### 3.1 전체 흐름

```
Phase 1: 수주서 실 데이터            Phase 2: 출고증/납품확인서 실 데이터
├── 1.1 사전 조사 (DB 조회)          ├── 2.1 사전 조사 (Shipment 관계 확인)
├── 1.2 API getOrderDetail 보강      ├── 2.2 API getShipmentDetail 보강
├── 1.3 SalesOrderDocument 수정      ├── 2.3 ShipmentOrderDocument 수정
├── 1.4 OrderDocumentModal 연동      ├── 2.4 QMS InspectionModal 연동
└── 1.5 QMS InspectionModal 연동     │   ├── case 'confirmation' → DeliveryConfirmation 래퍼
         ↓                           │   └── case 'shipping' → ShippingSlip 래퍼
         ↓                           └── 2.5 actions.ts transform 함수 추가
         ↓                                    ↓
    Phase 5: 통합 검증 (Phase 1-2 완료 후)
    ├── 5.1 수주 57/59 수주서 검증
    ├── 5.2 수주 페이지 OrderDocumentModal 검증
    ├── 5.3 출하 13 출고증/납품확인서 검증
    └── 5.4 QMS 로트 추적 전체 시나리오

    ─── ⏸️ Phase 3-4는 별도 계획으로 분리 ───
```

---

## 4. 상세 작업 내용

### Phase 1: 수주서 실 데이터 매핑

#### 1.1 사전 조사 (DB 조회)

> Phase 1 첫 단계로 실행. 결과에 따라 1.2의 데이터 추출 로직이 달라짐.

| # | 조사 항목 | SQL/방법 | 목적 |
|---|----------|---------|------|
| C1 | bom_result 변수키 | `SELECT id, JSON_EXTRACT(options, '$.bom_result.variables') FROM order_nodes WHERE order_id IN (57,59) AND parent_id IS NULL LIMIT 5` | products 매핑 키 확정 |
| C2 | 모터/절곡/부자재 데이터 출처 | C1 결과에서 motor/guide_rail/case 관련 키 존재 여부 확인 + `SELECT * FROM order_items WHERE order_id IN (57,59)` | 데이터 추출 경로 결정 |

**OrderNode vs OrderItem 차이** (참고):
- **OrderNode** = 설계/위치 뷰 (개소별 사이즈, BOM 계산 결과 in `options.bom_result.variables`)
- **OrderItem** = 자재/구매 뷰 (품목명, 규격, 수량, 단가 — 발주/출하에 사용)
- 두 테이블은 **관점이 다르지 저장이 중복되는 것이 아님**

---

#### 1.2 백엔드 — `getOrderDetail()` 보강

**파일**: `api/app/Services/QmsLotAuditService.php` (L416-431)

**현재 응답** (불충분):
```php
return [
    'type' => 'order',
    'data' => [
        'id', 'order_no', 'status', 'received_at', 'site_name',
        'nodes_count'  // ← 노드 개수만 반환
    ],
];
```

**보강 후 응답**:
```php
return [
    'type' => 'order',
    'data' => [
        // 기본 정보
        'id', 'order_no', 'status', 'received_at', 'site_name',
        'category_code',  // 제품 유형 (screen/steel)

        // 거래처 정보 (order.client 관계)
        'client_name',        // client.name
        'client_contact',     // client.contact_person 또는 options에서

        // 배송 정보
        'delivery_date',              // order.delivery_date
        'delivery_method_code',       // ⚠️ 실제 컬럼명 확인 필요 (delivery_method가 아닐 수 있음)
        'delivery_address',           // order.options.delivery_address

        // 담당자
        'manager_name',       // order.manager 관계 또는 options

        // 금액
        'total_quantity',     // rootNodes count
        'supply_amount',      // order.supply_amount
        'total_amount',       // order.total_amount

        // 메모
        'remarks',            // order.remarks

        // 개소별 제품 상세 (rootNodes → 변환)
        'products' => [
            [
                'no' => 1,
                'floor' => '10F',
                'code' => 'FA123',
                'product_name' => '스크린',       // ⚠️ SalesOrderDocument에서 사용됨 (L145)
                'open_width' => 4300,             // node.options.open_width
                'open_height' => 4300,            // node.options.open_height
                'made_width' => 4300,             // node.options.width (= 제작가로)
                'made_height' => 3000,            // node.options.height (= 제작세로)
                'guide_rail' => '백면형',          // bom_result.variables.{키 C1에서 확인}
                'shaft' => 5,
                'joint_bar' => null,              // 스크린은 null, 철재만
                'case_inch' => 5,
                'bracket' => '500X300',
                'capacity' => 300,
                'finish' => 'SUS마감',
            ],
        ],

        // 모터 정보 — ⚠️ 좌/우 2열 레이아웃 필요
        'motors' => [
            'left' => [
                ['item' => '모터', 'type' => '380V 단상', 'spec' => 'KD-150K', 'qty' => 6],
                ['item' => '브라켓트', 'type' => '-', 'spec' => '380X180', 'qty' => 6],
            ],
            'right' => [
                ['item' => '앵글', 'type' => '-', 'spec' => '50X50', 'qty' => 6],
                ['item' => '전동개폐기', 'type' => '-', 'spec' => 'KD-200', 'qty' => 6],
            ],
        ],

        // 절곡물 — ⚠️ 서브그룹 구조 필요 (가이드레일/케이스/하단마감 + 연기차단재)
        'bending_parts' => [
            [
                'group' => '가이드레일',
                'items' => [
                    ['name' => '가이드레일(백면형)', 'spec' => 'L: 3000', 'qty' => 22],
                ],
            ],
            [
                'group' => '케이스',
                'items' => [
                    ['name' => '케이스(5인치)', 'spec' => 'L: 4300', 'qty' => 10],
                ],
            ],
            [
                'group' => '하단마감',
                'items' => [
                    ['name' => '하단마감(알루미늄)', 'spec' => 'L: 4300', 'qty' => 5],
                ],
            ],
            [
                'group' => '연기차단재',  // ⚠️ MOCK_GUIDE_SMOKE, MOCK_CASE_SMOKE 대응
                'items' => [
                    ['name' => '가이드레일(연기차단재)', 'spec' => 'L: 3000', 'qty' => 4],
                    ['name' => '케이스(연기차단재)', 'spec' => 'L: 4300', 'qty' => 2],
                ],
            ],
        ],

        // 부자재 (BOM에서 집계)
        'subsidiary_parts' => [
            ['name' => '감기사프트', 'spec' => 'L: 4000', 'qty' => 22],
            ['name' => '조인트바', 'spec' => '-', 'qty' => 12],
        ],
    ],
];
```

**데이터 추출 로직**:
```
Order
├─ 기본: order.*, order.client.name, order.options.delivery_address 등
├─ products: rootNodes.map(node => {
│   node.options.floor / symbol / open_width / open_height
│   node.options.width (제작가로) / node.options.height (제작세로)
│   node.options.bom_result.variables.{C1에서 확인한 키} (guide_rail, shaft 등)
│ })
├─ motors: rootNodes.flatMap → bom_result.variables에서 집계 (좌/우 분리)
│   ⚠️ 좌/우 분리 규칙은 C1 조사 후 Mock 데이터(MOCK_MOTOR_LEFT/RIGHT) 참고하여 결정
├─ bending_parts: rootNodes.flatMap → bom_result.variables에서 그룹별 집계
│   ⚠️ 연기차단재(MOCK_GUIDE_SMOKE, MOCK_CASE_SMOKE) 포함 필수
└─ subsidiary_parts: order.items에서 부자재 카테고리 필터
```

**주의사항**:
- 누락 데이터는 null 반환 (프론트에서 '-' 표시)
- `bom_result.variables`의 실제 키 이름은 BOM 공식(QuoteFormula)이 동적 생성하므로 코드만으로 확정 불가 → C1 조사 필수
- **Order 모델에 없는 필드**: `client_phone`, `address`, `recipient_name`, `recipient_contact`, `manager_contact`, `fee` → 모델/관계 확인 후 존재하는 필드만 매핑

---

#### 1.3 프론트엔드 — `SalesOrderDocument.tsx` 수정

**파일**: `react/src/components/orders/documents/SalesOrderDocument.tsx`

**⚠️ 중요: 이 컴포넌트의 props 구조**

SalesOrderDocument는 **18개의 개별 props**를 받음 (단일 data 객체가 아님):
```typescript
interface SalesOrderDocumentProps {
  orderNumber?: string;
  orderDate?: string;
  client?: string;
  siteName?: string;
  // ... 18개 개별 props
  products?: ProductInfo[];  // ⚠️ L145에서 productName 표시에 사용됨 (미사용이 아님!)
  items?: OrderItem[];
}
```

**Mock 상수 목록** (12개 + 연기차단재 2개 = 14개):
| # | 상수명 | 대응 데이터 |
|---|--------|-----------|
| 1 | MOCK_SCREEN_ROWS | products (스크린) |
| 2 | MOCK_STEEL_ROWS | products (철재) |
| 3 | MOCK_MOTOR_LEFT | motors.left |
| 4 | MOCK_MOTOR_RIGHT | motors.right |
| 5 | MOCK_GUIDE_RAIL | bending_parts[가이드레일] |
| 6 | MOCK_CASE | bending_parts[케이스] |
| 7 | MOCK_BOTTOM_FINISH | bending_parts[하단마감] |
| 8 | MOCK_GUIDE_SMOKE | bending_parts[연기차단재-가이드] |
| 9 | MOCK_CASE_SMOKE | bending_parts[연기차단재-케이스] |
| 10 | MOCK_SUBSIDIARY | subsidiary_parts |
| 11 | MOCK_SUMMARY_LEFT | 합계 (좌) |
| 12 | MOCK_SUMMARY_RIGHT | 합계 (우) |

**수정 내용**:

| # | 작업 | 상태 |
|---|------|:----:|
| 1.3.1 | MOCK_* 상수 전체 제거 (14개) | ⏳ |
| 1.3.2 | API 응답 → 18개 개별 props 매핑 로직 작성 | ⏳ |
| 1.3.3 | 제품 유형별 분기 (스크린 vs 철재) | ⏳ |
| 1.3.4 | 모터 좌/우 2열 레이아웃 실 데이터 렌더링 | ⏳ |
| 1.3.5 | 절곡물 서브그룹별 (가이드/케이스/하단/연기차단재) 렌더링 | ⏳ |
| 1.3.6 | 부자재 섹션 실 데이터 렌더링 | ⏳ |
| 1.3.7 | 누락 데이터 '-' 표시 (에러 방지) | ⏳ |

**데이터 매핑** (API → 18개 개별 props):
```typescript
// API 응답을 SalesOrderDocument의 개별 props로 매핑
function mapOrderToProps(data: OrderDetailResponse): SalesOrderDocumentProps {
  return {
    orderNumber: data.order_no,
    client: data.client_name,
    siteName: data.site_name,
    orderDate: data.received_at,
    deliveryRequestDate: data.delivery_date,
    deliveryMethod: data.delivery_method_code,
    manager: data.manager_name,
    // products → products prop (productName 표시에 사용됨!)
    products: data.products?.map(p => ({
      productName: p.product_name,
      // ... 기타 ProductInfo 필드
    })),
    // ... 나머지 props
  };
}
```

---

#### 1.4 수주 페이지 연동 — `OrderDocumentModal.tsx`

**파일**: `react/src/components/orders/documents/OrderDocumentModal.tsx` (L167-190)

> ⚠️ **SalesOrderDocument는 이중 사용 (dual-use)**:
> - QMS InspectionModal에서 수주서 렌더링
> - **수주 페이지 OrderDocumentModal에서도 수주서 렌더링**
>
> 둘 다 동일한 실 데이터 매핑이 적용되어야 함.

**수정**: OrderDocumentModal에서도 API 실 데이터로 SalesOrderDocument 렌더링

---

#### 1.5 QMS 연동 — `InspectionModal.tsx` 수주서 케이스

**파일**: `react/src/app/[locale]/(protected)/quality/qms/components/InspectionModal.tsx`

**현재**: `case 'order'`에서 QMS_MOCK_* 데이터로 `SalesOrderDocument` 렌더링

**수정**:
```typescript
case 'order':
  // 기존: QMS_MOCK_* 데이터 사용
  // 변경: getDocumentDetail('order', id) 호출 → 실 데이터 전달
  const orderData = await getDocumentDetail('order', item.id);
  const mappedProps = mapOrderToProps(orderData.data);
  return <SalesOrderDocument {...mappedProps} />;
```

---

### Phase 2: 출고증/납품확인서 실 데이터 매핑

#### 2.1 사전 조사

| # | 조사 항목 | 방법 | 목적 |
|---|----------|------|------|
| C3 | Shipment 관계 구조 | `Shipment::with(['vehicleDispatches', 'items'])->find(13)` | 관계 로드 확인 |
| C3b | ShipmentItem → item 관계 | ShipmentItem 모델에 `item()` belongsTo 관계 존재 여부 확인 | eager loading 가능 여부 |

---

#### 2.2 백엔드 — `getShipmentDetail()` 보강

**파일**: `api/app/Services/QmsLotAuditService.php` (L450-470)

**현재 응답** (불충분):
```php
return [
    'type' => 'shipping',
    'data' => [
        'id', 'shipment_no', 'status', 'scheduled_date',
        'customer_name', 'site_name', 'delivery_address',
        'delivery_method', 'vehicle_no', 'driver_name', 'remarks'
    ],
];
```

**보강 후 응답**:
```php
return [
    'type' => 'shipping',
    'data' => [
        // 기본 정보
        'id', 'shipment_no', 'lot_no', 'status',
        'scheduled_date',
        // ⚠️ shipment_date 컬럼 존재 여부 확인 필요 (없을 수 있음)
        'customer_name', 'customer_grade',
        'site_name',
        // ⚠️ registrant, orderer 컬럼 존재 여부 확인 필요

        // 배송 정보
        'delivery_method',
        'shipping_cost',      // ⚠️ freight_cost가 아님! 실제 컬럼명 = shipping_cost
        'receiver', 'receiver_contact',
        'delivery_address',
        // ⚠️ zip_code, address_detail은 별도 컬럼이 아닐 수 있음 (options 확인)

        // 배차정보 (출고증 전용)
        'vehicle_dispatches' => [
            [
                'logistics_company',
                'arrival_datetime',   // ⚠️ arrival_date가 아님! 실제 = arrival_datetime
                'tonnage',
                'vehicle_no',
                'driver_contact',
                'remarks',
            ],
        ],

        // 제품 그룹
        'product_groups' => [
            [
                'product_name' => '스크린',
                'specification' => '...',
                'part_count' => 5,
                'parts' => [
                    ['item_name', 'specification', 'quantity', 'unit'],
                ],
            ],
        ],
        'other_parts' => [...],
    ],
];
```

**주의**: Shipment 모델의 관계 로드 필요:
```php
$shipment = Shipment::with([
    'vehicleDispatches',
    'items.item',           // ⚠️ ShipmentItem에 item() 관계가 없으면 추가 필요
    'order.rootNodes',      // 수주 개소 정보
])->findOrFail($id);
```

**⚠️ Shipment 모델 필드 확인 필요 목록**:
| 계획상 필드 | 확인 사항 |
|------------|----------|
| `shipment_date` | 컬럼 존재 여부 (scheduled_date만 있을 수 있음) |
| `registrant` | 컬럼 존재 여부 |
| `orderer` | 컬럼 존재 여부 |
| `shipping_cost` | ✅ 실제 컬럼명 (`freight_cost` 아님) |
| `zip_code` | 별도 컬럼 vs options JSON |
| `address_detail` | 별도 컬럼 vs options JSON |

---

#### 2.3 프론트엔드 — `ShipmentOrderDocument.tsx` 수정

**파일**: `react/src/components/outbound/ShipmentManagement/documents/ShipmentOrderDocument.tsx`

**현재**: MOCK_* 상수 12개 사용 (SalesOrderDocument와 중복된 MOCK 상수들)

**⚠️ 중요: props 구조**
- `data: ShipmentDetail` 단일 props — **헤더 정보에만 사용됨**
- 제품 테이블은 data 내의 productGroups/parts에서 매핑
- `showDispatchInfo` / `showLotColumn` boolean 토글

| # | 작업 | 상태 |
|---|------|:----:|
| 2.3.1 | MOCK_* 상수 제거 (12개) | ⏳ |
| 2.3.2 | data.productGroups → 제품 테이블 매핑 | ⏳ |
| 2.3.3 | data.vehicleDispatches → 배차정보 매핑 | ⏳ |
| 2.3.4 | showDispatchInfo/showLotColumn 조건부 렌더링 유지 | ⏳ |

**출고증 vs 납품확인서 차이**:
- 출고증: `showDispatchInfo=true`, `showLotColumn=true`
- 납품확인서: `showDispatchInfo=false`, `showLotColumn=false`

---

#### 2.4 QMS 연동 — `InspectionModal.tsx` 출고증/납품확인서 케이스

> ⚠️ **InspectionModal은 래퍼 컴포넌트를 사용함** (ShipmentOrderDocument를 직접 렌더링하지 않음)

**현재 구조**:
```typescript
case 'confirmation':
  return <DeliveryConfirmation ... />;  // ← 래퍼 컴포넌트
case 'shipping':
  return <ShippingSlip ... />;          // ← 래퍼 컴포넌트
```

**수정**: 래퍼 컴포넌트(DeliveryConfirmation, ShippingSlip)가 내부적으로 ShipmentOrderDocument에 실 데이터를 전달하도록 수정

```typescript
case 'confirmation':
  const confData = await getDocumentDetail('confirmation', item.id);
  return <DeliveryConfirmation data={confData.data} />;

case 'shipping':
  const shipData = await getDocumentDetail('shipping', item.id);
  return <ShippingSlip data={shipData.data} />;
```

> DeliveryConfirmation → `<ShipmentOrderDocument showDispatchInfo={false} showLotColumn={false} />`
> ShippingSlip → `<ShipmentOrderDocument showDispatchInfo={true} showLotColumn={true} />`

---

#### 2.5 actions.ts transform 함수 추가

**파일**: `react/src/app/[locale]/(protected)/quality/qms/actions.ts`

**현재**: `getDocumentDetail`이 API 응답을 그대로 반환 (snake_case raw response, transform 없음)

**추가 필요**:
```typescript
// snake_case → camelCase 변환 + 타입 매핑
function transformOrderDetail(raw: any): OrderDetailForDocument { ... }
function transformShipmentDetail(raw: any): ShipmentDetailForDocument { ... }
```

---

### Phase 3: ⏸️ 보류 — mng 양식 템플릿 생성

> **보류 사유**: `document_templates` 스키마는 검사 성적서용 설계 (고정 행 + EAV 패턴).
> 수주서/출고증은 **동적 행 수** + **제품유형별 다른 컬럼** + **서브그룹 구조**를 가지므로 현재 스키마로는 부적합.
>
> Phase 1-2 완료 후, 양식 구조를 별도 설계하거나 document_templates 스키마를 확장하는 방안을 재검토.
>
> **재검토 시 확인 사항**:
> - C4: mng 양식 편집기가 다중 섹션/조건부 섹션을 지원하는지
> - C5: 양식 카테고리 명명 패턴

---

### Phase 4: ⏸️ 보류 — 양식 기반 렌더링 전환

> Phase 3과 동일한 사유로 보류.

---

### Phase 5: 통합 검증

#### 5.1 검증 시나리오

| # | 시나리오 | 입력 | 기대 결과 |
|---|---------|------|----------|
| T1 | 수주 57 수주서 | 스크린 제품 | 개소별 사이즈, 모터(좌/우), 절곡(서브그룹), 부자재 실 데이터 |
| T2 | 수주 59 수주서 | 철재 제품 | 조인트바 컬럼 추가, 철재 전용 데이터 |
| T3 | 수주 57 OrderDocumentModal | 수주 페이지 | T1과 동일한 실 데이터 렌더링 |
| T4 | 출하 13 출고증 | 배차정보 포함 | 배차정보 + LOT 컬럼 표시 |
| T5 | 출하 13 납품확인서 | 배차정보 미포함 | 배차/LOT 컬럼 숨김 |
| T6 | QMS 수주서 모달 | 로트 추적 심사 | 실 데이터 기반 수주서 렌더링 |
| T7 | QMS 출고증 모달 | 로트 추적 심사 | 실 데이터 기반 출고증 렌더링 |
| T8 | QMS 납품확인서 모달 | 로트 추적 심사 | 실 데이터 기반 납품확인서 렌더링 |

#### 5.2 Phase 1-2 완료 후 서류 상태

| # | 문서 | Phase 1-2 후 | 비고 |
|---|------|:----------:|------|
| 1 | 수입검사 성적서 | ✅ 완료 | 기존 유지 |
| 2 | 수주서 | ✅ 실 데이터 | Phase 1 |
| 3 | 작업일지 | 🔄 공정별 | 별도 계획 |
| 4 | 중간검사 성적서 | 🔄 공정별 | 별도 계획 |
| 5 | 납품확인서 | ✅ 실 데이터 | Phase 2 |
| 6 | 출고증 | ✅ 실 데이터 | Phase 2 |
| 7 | 제품검사 성적서 | ✅ 완료 | 기존 유지 |
| 8 | 품질관리서 | PDF 업로드 | PDF 유지 |

---

## 5. 사전 조사 목록

> C1~C3b는 각 Phase 시작 시 DB 조회로 직접 확인. C4~C5는 Phase 3 재검토 시 확인.

| # | 항목 | 조사 방법 | 시점 | 상태 |
|---|------|----------|------|------|
| C1 | bom_result 변수키 확인 | `SELECT id, JSON_EXTRACT(options, '$.bom_result.variables') FROM order_nodes WHERE order_id IN (57,59) AND parent_id IS NULL LIMIT 5` | Phase 1.1 시작 시 | ⏳ |
| C2 | 모터/절곡/부자재 데이터 출처 | C1 결과에서 motor/guide_rail/case 키 확인 + `SELECT * FROM order_items WHERE order_id IN (57,59)` | Phase 1.1 시작 시 | ⏳ |
| C3 | Shipment 관계 구조 | `Shipment::with(['vehicleDispatches', 'items'])->find(13)` 관계 확인 | Phase 2.1 시작 시 | ⏳ |
| C3b | ShipmentItem.item() 관계 | ShipmentItem 모델에 `item()` belongsTo 관계 존재 여부 확인 | Phase 2.1 시작 시 | ⏳ |
| C4 | mng 양식 편집기 확장 범위 | mng Blade 코드 분석 — 다중 섹션/조건부 섹션 지원 여부 | Phase 3 재검토 시 | ⏸️ |
| C5 | 양식 카테고리 명명 | 기존 document_templates.category 값 패턴 확인 후 결정 | Phase 3 재검토 시 | ⏸️ |

---

## 6. 검증 분석 결과 (계획 검증)

> 2026-03-12 검증 수행 결과. 계획 정확도 향상을 위해 반영됨.

### 6.1 CRITICAL 이슈 (계획에 반영 완료)

| # | 이슈 | 내용 | 조치 |
|---|------|------|------|
| C-1 | Order 필드명 불일치 | `client_phone`, `address`, `recipient_name`, `recipient_contact`, `manager_contact`, `fee` — Order 모델에 없는 필드 | 제거/조건부로 변경 |
| C-2 | Shipment 필드명 불일치 | `shipment_date`, `registrant`, `orderer`, `zip_code`, `address_detail` 미확인, `freight_cost` → `shipping_cost` | 필드명 수정 + 확인필요 표시 |
| C-3 | SalesOrderDocument props | 단일 data 객체가 아닌 18개 개별 props | props 매핑 방식 수정 |
| C-4 | InspectionModal 래퍼 | `case 'confirmation'` → DeliveryConfirmation, `case 'shipping'` → ShippingSlip 래퍼 사용 | 래퍼 컴포넌트 경유로 수정 |
| C-5 | arrival_datetime | `arrival_date`가 아닌 `arrival_datetime`이 실제 컬럼명 | 수정 완료 |

### 6.2 HIGH 이슈 (계획에 반영 완료)

| # | 이슈 | 내용 | 조치 |
|---|------|------|------|
| H-1 | 연기차단재 Mock 누락 | MOCK_GUIDE_SMOKE, MOCK_CASE_SMOKE 미반영 | bending_parts에 연기차단재 그룹 추가 |
| H-2 | products prop 사용됨 | "미사용"이 아닌 L145에서 productName 표시에 활용 | product_name 필드 매핑 추가 |
| H-3 | OrderDocumentModal 이중사용 | SalesOrderDocument가 QMS + 수주 페이지 양쪽에서 사용 | Phase 1.4 작업 항목 추가 |
| H-4 | bending_parts 서브그룹 | 단순 flat 배열로는 가이드/케이스/하단/연기차단재 구분 불가 | group 필드 추가한 중첩 구조로 변경 |
| H-5 | motors 좌/우 분리 | 단일 배열로는 2열 레이아웃 불가 | left/right 분리 구조로 변경 |
| H-6 | ShipmentItem.item() | ShipmentItem에 `item()` belongsTo 관계 미확인 | C3b 사전 조사 추가 |
| H-7 | actions.ts transform | getDocumentDetail에 transform 함수 없음 (raw snake_case 반환) | Phase 2.5 작업 추가 |

### 6.3 아키텍처 이슈 (Phase 3-4 보류 근거)

**document_templates 스키마 부적합**:
- 현재 스키마: 고정 행 수 + EAV(Entity-Attribute-Value) 패턴 → 검사 성적서에 최적화
- 수주서/출고증: 동적 행 수 + 제품유형별 다른 컬럼 + 서브그룹(모터 좌/우, 절곡 그룹) 구조
- **결론**: Phase 1-2 (실 데이터 매핑)를 먼저 완료하고, Phase 3-4는 양식 구조를 재설계한 후 진행

---

## 7. 변경 이력

| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|------|------|----------|------|------|
| 2026-03-12 | 러프 계획 | 초안 작성 (5 Phase 구조) | 이 문서 | - |
| 2026-03-12 | 상세 계획 | Phase 1-5 상세 작업 내용 추가 | 이 문서 | - |
| 2026-03-12 | 계획 확정 | 컨펌→사전조사 전환, bom_result 설명 보강 | 이 문서 | ✅ |
| 2026-03-12 | 검증 반영 | C1-5, H1-7 이슈 반영, Phase 3-4 보류, 필드명 수정, 구조 보강 | 이 문서 | ✅ |

---

## 8. 참고 문서

| 문서 | 역할 |
|------|------|
| `docs/INDEX.md` | 문서 인덱스 |
| `docs/features/quality-management/README.md` | 품질관리 시스템 전체 구조 |
| `docs/system/database/documents.md` | 문서 템플릿 DB 스키마 |
| `docs/system/database/sales.md` | 수주/견적 DB 스키마 |
| `docs/dev/standards/api-rules.md` | API 개발 규칙 |
| `docs/dev/standards/quality-checklist.md` | 코드 품질 체크리스트 |

---

## 9. 참고 파일 (코드)

| 파일 | 역할 | 비고 |
|------|------|------|
| `api/app/Services/QmsLotAuditService.php` | QMS 서류 API | 수정 대상 (L416-431, L450-470) |
| `react/.../orders/documents/SalesOrderDocument.tsx` | 수주서 컴포넌트 | 수정 대상 (18개 개별 props) |
| `react/.../orders/documents/OrderDocumentModal.tsx` | 수주 페이지 문서 모달 | 수정 대상 (dual-use, L167-190) |
| `react/.../outbound/.../ShipmentOrderDocument.tsx` | 출고증/납품확인서 | 수정 대상 (data prop) |
| `react/.../quality/qms/components/InspectionModal.tsx` | QMS 문서 뷰어 | 수정 대상 (래퍼 컴포넌트 경유) |
| `react/.../quality/qms/actions.ts` | QMS 서버 액션 | 수정 대상 (transform 추가) |
| `react/.../quality/qms/components/documents/ImportInspectionDocument.tsx` | 수입검사 | 참고 패턴 |
| `react/.../quality/InspectionManagement/documents/InspectionReportDocument.tsx` | 제품검사 | 참고 패턴 |
| `api/app/Models/Documents/DocumentTemplate.php` | 양식 모델 | 참고 |
| `api/app/Models/Orders/Order.php` | 수주 모델 | 필드 확인 필요 |
| `api/app/Models/Orders/OrderNode.php` | 수주 노드 모델 | bom_result 구조 |
| `api/app/Models/Tenants/Shipment.php` | 출하 모델 | 필드 확인 필요 |
| `api/app/Models/Tenants/ShipmentItem.php` | 출하 품목 | item() 관계 확인 |
| `api/app/Models/Tenants/ShipmentVehicleDispatch.php` | 배차 모델 | arrival_datetime |
| `api/app/Services/OrderService.php` | 수주 서비스 | 데이터 로드 패턴 참고 |
| `api/app/Services/ShipmentService.php` | 출하 서비스 | 데이터 로드 패턴 참고 |

---

## 10. 세션 및 메모리 관리 정책 (Serena Optimized)

### 10.1 세션 시작 시 (Load Strategy)
```
read_memory("qms-doc-template-state")          // 1. 상태 파악
read_memory("qms-doc-template-snapshot")        // 2. 사고 흐름 복구
read_memory("qms-doc-template-active-symbols")  // 3. 작업 대상 파악
```

### 10.2 작업 중 관리 (Context Defense)
| 컨텍스트 잔량 | Action | 내용 |
|--------------|--------|------|
| **30% 이하** | Snapshot | `write_memory("qms-doc-template-snapshot", "코드변경+논의요약")` |
| **20% 이하** | Context Purge | `write_memory("qms-doc-template-active-symbols", "주요 수정 파일/함수")` |
| **10% 이하** | Stop & Save | 최종 상태 저장 후 세션 교체 권고 |

### 10.3 Serena 메모리 구조
- `qms-doc-template-state`: { phase, progress, next_step, last_decision }
- `qms-doc-template-snapshot`: 현재까지의 논의 및 코드 변경점 요약
- `qms-doc-template-rules`: 해당 작업에서 결정된 불변의 규칙들
- `qms-doc-template-active-symbols`: 현재 수정 중인 파일/심볼 리스트

---

## 11. 검증 결과

> 작업 완료 후 이 섹션에 검증 결과 추가

### 11.1 테스트 케이스

| 입력값 | 예상 결과 | 실제 결과 | 상태 |
|--------|----------|----------|------|
| 수주 57 (스크린) 수주서 | 개소별 실 데이터 + 모터 좌/우 + 절곡 서브그룹 | | ⏳ |
| 수주 59 (철재) 수주서 | 조인트바 포함 실 데이터 | | ⏳ |
| 수주 57 OrderDocumentModal | 수주 페이지에서 동일 렌더링 | | ⏳ |
| 출하 13 출고증 | 배차정보 + 제품 실 데이터 | | ⏳ |
| 출하 13 납품확인서 | 제품 실 데이터 (배차 제외) | | ⏳ |
| QMS 전체 시나리오 | 8종 서류 실 데이터 확인 | | ⏳ |

---

## 12. 자기완결성 점검 결과

### 12.1 체크리스트 검증

| # | 검증 항목 | 상태 | 비고 |
|---|----------|:----:|------|
| 1 | 작업 목적이 명확한가? | ✅ | Mock 제거 → 실 데이터 (Phase 1-2) |
| 2 | 성공 기준이 정의되어 있는가? | ✅ | SC-1~3b (Phase 1-2), SC-4~5 보류 |
| 3 | 작업 범위가 구체적인가? | ✅ | Phase 1-2, 12개 세부 작업 |
| 4 | 의존성이 명시되어 있는가? | ✅ | Phase 간 의존성 + 사전 조사 의존 |
| 5 | 참고 파일 경로가 정확한가? | ✅ | 섹션 8, 9에 전체 목록 + 라인 번호 |
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 코드 수준 상세 + 검증 이슈 반영 |
| 7 | 검증 방법이 명시되어 있는가? | ✅ | Phase 5 검증 시나리오 T1-T8 |
| 8 | 모호한 표현이 없는가? | ⚠️ | C1-C3b 사전 조사 필요 (의도적) |

### 12.2 새 세션 시뮬레이션 테스트

| 질문 | 답변 가능 | 참조 섹션 |
|------|:--------:|----------|
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경, 1.3 목표 |
| Q2. 어디서부터 시작해야 하는가? | ✅ | 3.1 전체 흐름, Phase 1.1 사전 조사 |
| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 9. 참고 파일 |
| Q4. 작업 완료 확인 방법은? | ✅ | 1.4 성공 기준, 11. 검증 결과 |
| Q5. 막혔을 때 참고 문서는? | ✅ | 8. 참고 문서 |
| Q6. 어떤 필드명이 잘못될 수 있는가? | ✅ | 6. 검증 분석 결과 |

---

*이 문서는 /plan 스킬로 생성되었습니다.*
