From bac20a093ee8b719aec612b712e1752839c5cec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 12 Mar 2026 19:05:56 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[QMS]=20=EB=AC=B8=EC=84=9C=20=EC=96=91?= =?UTF-8?q?=EC=8B=9D=20=EC=97=B0=EB=8F=99=20=EC=83=81=EC=84=B8=20=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=20=EC=88=98=EB=A6=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수주서/출고증/납품확인서 Mock→실데이터 전환 계획 - Phase 1-2 (실 데이터 매핑) 상세화, Phase 3-4 보류 - 검증 분석 반영 (필드명 수정, 구조 보강, 래퍼 컴포넌트 경유) --- .../qms-document-template-integration-plan.md | 775 ++++++++++++++++++ 1 file changed, 775 insertions(+) create mode 100644 dev/dev_plans/qms-document-template-integration-plan.md diff --git a/dev/dev_plans/qms-document-template-integration-plan.md b/dev/dev_plans/qms-document-template-integration-plan.md new file mode 100644 index 0000000..8b71aad --- /dev/null +++ b/dev/dev_plans/qms-document-template-integration-plan.md @@ -0,0 +1,775 @@ +# 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 ; +``` + +--- + +### 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 ; // ← 래퍼 컴포넌트 +case 'shipping': + return ; // ← 래퍼 컴포넌트 +``` + +**수정**: 래퍼 컴포넌트(DeliveryConfirmation, ShippingSlip)가 내부적으로 ShipmentOrderDocument에 실 데이터를 전달하도록 수정 + +```typescript +case 'confirmation': + const confData = await getDocumentDetail('confirmation', item.id); + return ; + +case 'shipping': + const shipData = await getDocumentDetail('shipping', item.id); + return ; +``` + +> DeliveryConfirmation → `` +> ShippingSlip → `` + +--- + +#### 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 스킬로 생성되었습니다.*