# 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 스킬로 생성되었습니다.*