# Phase 2: 절곡 검사 분석/설계 + 견적/품질 개선 > **통합 계획**: [`integrated-master-plan.md`](./integrated-master-plan.md) > **원본**: > - [`document-system-improvement-plan.md`](./document-system-improvement-plan.md) Phase 1 > - [`product-code-traceability-plan.md`](./product-code-traceability-plan.md) Phase 2, 3 > **상태**: ⏳ 실행 대기 > **의존성**: Phase 2A는 독립 (Phase 1과 병렬 가능), Phase 2B는 Phase 1 완료 필수 --- ## 1. Phase 2A: 절곡 검사 분석/설계 **목표**: 절곡 구성품(검사 항목) 정보를 API에서 제공하는 구조 설계 **Phase 1과 병렬 가능** (분석 전용, 코드 변경 없음) ### 1.1 작업 항목 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2A.1 | 절곡 제품코드별 구성품(BOM) 데이터 구조 분석 | ⏳ | `items`/BOM 테이블에서 KWE01/KSS01/KSS02 확인 | | 2A.2 | 마감유형(S1/S2/S3)별 차이 분석 | ⏳ | 5130 레거시 참조 | | 2A.3 | `inspection-config` 범용 API 설계 | ⏳ | `GET /api/v1/work-orders/{id}/inspection-config` (I5 정책) | | 2A.4 | `DEFAULT_GAP_PROFILES` 기준치 5130 대조 | ⏳ | I1: Single Source of Truth 보정 | ### 1.2 구성품 데이터 소스 분석 대상 ``` 분석 대상: 1. items 테이블 — type='finished_goods' 또는 'component'인 항목 2. bom_items 테이블 — 제품→구성품 관계 3. order_nodes.options.bending_info — 수주 시 절곡 정보 4. 5130/estimate/common/common_addrowJS.php — 레거시 구성품 정의 5. mng 절곡 양식(ID:13)의 section_items — 검사기준서 항목 6. TemplateInspectionContent DEFAULT_GAP_PROFILES — 현재 기준치 ``` ### 1.3 구성품 결정 로직 (설계안) ``` 입력: work_order_id ↓ 1차: 작업지시 → 공정 자동 판별 (inspection-config API) ↓ 2차: product_code → BOM 테이블에서 하위 구성품 조회 ↓ (BOM 미등록 시) 3차: DEFAULT_GAP_PROFILES 기본값 사용 ↓ (템플릿 미설정 시 = 레거시) 4차: INITIAL_PRODUCTS fallback (BendingInspectionContent, KWE01 하위호환) ``` 각 단계의 역할은 다음과 같다. - **1차 (공정 판별)**: `work_order_id`로부터 작업지시의 공정 타입(`bending`, `screen`, `slat`)을 자동 판별한다. 비절곡 공정이면 빈 `items` 배열을 반환하고 종료한다. - **2차 (BOM 조회)**: 해당 제품코드의 BOM에 등록된 구성품 목록을 조회한다. BOM 데이터가 있으면 이를 기준으로 검사 항목을 구성한다. - **3차 (기본 프로파일)**: BOM에 구성품이 등록되지 않은 경우, `DEFAULT_GAP_PROFILES`에 정의된 기본 갭 기준치를 사용한다. - **4차 (레거시 fallback)**: 템플릿 설정도 없는 경우, 기존 `BendingInspectionContent`의 `INITIAL_PRODUCTS` 7개 항목을 KWE01 전용 하위호환으로 사용한다. ### 1.4 inspection-config API 설계안 (I5 정책 결정) ``` GET /api/v1/work-orders/{id}/inspection-config ※ BelongsToTenant 스코프 필수 (M1) ※ 공정 타입 자동 판별 ``` **절곡 Response**: ```json { "data": { "work_order_id": 123, "process_type": "bending", "product_code": "FG-KQTS01", "finish_type": "S1", "template_id": 60, "items": [ { "id": "guide-rail-wall", "category": "KWE01", "product_name": "가이드레일", "product_type": "벽면형", "length_design": "3000", "width_design": "N/A", "gap_points": [ { "point": "1", "design_value": "30" }, { "point": "2", "design_value": "78" } ] } ] } } ``` **비절곡 Response (스크린/슬랫)**: ```json { "data": { "work_order_id": 456, "process_type": "screen", "product_code": "FG-KQTS01", "template_id": 12, "items": [] } } ``` **응답 필드 설명**: | 필드 | 타입 | 설명 | |------|------|------| | `work_order_id` | `integer` | 작업지시 ID | | `process_type` | `string` | 공정 타입 (`bending`, `screen`, `slat`) | | `product_code` | `string` | 제품코드 | | `finish_type` | `string\|null` | 마감유형 (절곡 전용: `S1`, `S2`, `S3`) | | `template_id` | `integer\|null` | 검사 양식 ID | | `items` | `array` | 검사 대상 구성품 목록 (비절곡 시 빈 배열) | | `items[].id` | `string` | 구성품 식별자 (kebab-case) | | `items[].category` | `string` | 제품 카테고리 코드 | | `items[].product_name` | `string` | 구성품 명칭 | | `items[].product_type` | `string` | 구성품 유형/규격 | | `items[].length_design` | `string` | 설계 길이 | | `items[].width_design` | `string` | 설계 폭 (`N/A` 가능) | | `items[].gap_points` | `array` | 갭 측정 포인트 목록 | ### 1.5 현재 하드코딩 현황 (레거시 동결 -- C3) `BendingInspectionContent.tsx`의 `INITIAL_PRODUCTS` (7개, KWE01 전용): | # | 항목 ID | productName | productType | gapPoints 수 | |---|---------|-------------|-------------|:----------:| | 1 | `guide-rail-wall` | 가이드레일 | 벽면형 | 5 | | 2 | `guide-rail-side` | 가이드레일 | 측면형 | 5 | | 3 | `case` | 케이스 | 500X380 | 4 | | 4 | `bottom-finish` | 하단마감재 | 60X40 | 2 | | 5 | `bottom-l-bar` | 하단L-BAR | 17X60 | 1 | | 6 | `smoke-w50` | 연기차단재 | W50 가이드레일용 | 2 | | 7 | `smoke-w80` | 연기차단재 | W80 케이스용 | 2 | Phase 2A 분석 완료 후, 이 하드코딩 항목들은 `inspection-config` API 응답으로 대체될 예정이다. 현재는 C3(레거시 동결) 정책에 따라 수정하지 않는다. --- ## 2. Phase 2B: 견적/수주 정합성 + 품질검사 FK **목표**: `quotes.product_code` 활용 + `inspections` ↔ `work_orders` FK 연결 **선행 조건**: Phase 1 완료 **내부 병렬성**: 2B-견적과 2B-품질은 독립 경로 ``` Phase 2B 내부 구조: Phase 1 완료 | +----+----+ | | 2B-견적 2B-품질 (2B.1~3) (2B.4~7) | | +----+----+ | Phase 2B 완료 ``` ### 2.1 견적 데이터 정합성 (원본 Phase 2) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2B.1 | 견적 저장 시 `quotes.product_code` 저장 | ⏳ | 다중 개소: 첫 번째 개소 코드 대표 저장 | | 2B.2 | 견적→수주 변환 시 `camelCase`→`snake_case` 변환 확인 | ⏳ | `OrderService::createFromQuote` | | 2B.3 | 기존 데이터 보정 스크립트 | ⏳ | `calculation_inputs`에서 추출 | **다중 개소 정책**: `quotes.product_code`에는 첫 번째 개소 코드를 대표값으로 저장한다. 전체 목록은 `calculation_inputs.items[].productCode`를 참조한다. **의존성 주의**: `orders.item_id` 설정은 `items` 테이블에 FG 품목 등록이 필요하므로 Phase 5에서 처리한다. **데이터 보정 스크립트 상세**: ```php // 2B.3 보정 로직 개요 // quotes 테이블에서 product_code가 null인 레코드 대상 // calculation_inputs JSON에서 items[0].productCode 추출하여 저장 $quotes = Quote::whereNull('product_code') ->whereNotNull('calculation_inputs') ->get(); foreach ($quotes as $quote) { $inputs = json_decode($quote->calculation_inputs, true); $productCode = $inputs['items'][0]['productCode'] ?? null; if ($productCode) { $quote->update(['product_code' => $productCode]); } } ``` ### 2.2 품질검사 연결 강화 (원본 Phase 3) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2B.4 | `inspections` 테이블에 `work_order_id` FK 마이그레이션 | ⏳ | `nullable` | | 2B.5 | `Inspection` 모델에 `workOrder()` 관계 메서드 추가 | ⏳ | N+1 방지 | | 2B.6 | 품질검사 생성 시 `work_order_id` 설정 로직 | ⏳ | `InspectionService` | | 2B.7 | 기존 `inspections` 데이터에 `work_order_id` 보정 | ⏳ | `lot_no` 기반 역추적 (중복 사전 확인) | **마이그레이션 설계 (2B.4)**: ```php Schema::table('inspections', function (Blueprint $table) { $table->unsignedBigInteger('work_order_id')->nullable()->after('id'); $table->foreign('work_order_id') ->references('id') ->on('work_orders') ->nullOnDelete(); $table->index('work_order_id'); }); ``` **모델 관계 (2B.5)**: ```php // Inspection.php public function workOrder(): BelongsTo { return $this->belongsTo(WorkOrder::class); } // WorkOrder.php public function inspections(): HasMany { return $this->hasMany(Inspection::class); } ``` **역추적 보정 로직 (2B.7)**: ``` inspections.lot_no → work_order_items.lot_no → work_orders.id | 중복 검사: 동일 lot_no에 다수 work_order 매칭 시 경고 로그 | MATCH: work_order_id 설정 NO_MATCH: null 유지 (수동 보정 대상) ``` --- ## 3. 검증 결과 ### 3.1 Phase 2A 검증 | 조사 항목 | 결과 | 판단 | |----------|------|------| | KWE01 BOM 구성품 수 | | ⏳ | | KSS01 BOM 구성품 수 | | ⏳ | | KSS02 BOM 구성품 수 | | ⏳ | | 마감유형별 차이점 | | ⏳ | | `DEFAULT_GAP_PROFILES` 5130 대조 | | ⏳ | ### 3.2 Phase 2B 검증 | 테스트 | 예상 결과 | 실제 결과 | 상태 | |--------|----------|----------|------| | 견적 저장 시 `quotes.product_code` | 첫 번째 개소 코드 | | ⏳ | | 다중 개소 대표 코드 | 첫 번째 개소 | | ⏳ | | 견적→수주 변환 `camelCase`→`snake_case` | 정상 변환 | | ⏳ | | `inspections.work_order_id` FK 마이그레이션 | 성공, `nullable` | | ⏳ | | 기존 inspection 조회 회귀 | 정상 | | ⏳ | | `lot_no` 기반 역추적 보정 정확도 | MATCH 95% 이상 | | ⏳ | --- ## 4. 참고 파일 ### 4.1 Phase 2A 관련 | 파일 | 역할 | |------|------| | `react/.../documents/TemplateInspectionContent.tsx` | `DEFAULT_GAP_PROFILES` (L176-206), `buildBendingProducts` (L209-274) | | `react/.../documents/BendingInspectionContent.tsx` | `INITIAL_PRODUCTS` (L71-135, 레거시 동결) | | `5130/estimate/common/common_addrowJS.php` | 레거시 구성품 정의 | | `5130/output/view_inspection_bending.php` | 절곡 중간검사 성적서 | ### 4.2 Phase 2B 관련 | 파일 | 역할 | |------|------| | `api/app/Services/Quote/QuoteService.php` | 견적 서비스 (`product_code` L324) | | `api/app/Services/InspectionService.php` | 품질검사 서비스 | | `api/app/Models/Quality/Inspection.php` | 검사 모델 | --- ## 5. 변경 이력 | 날짜 | 항목 | 변경 내용 | |------|------|----------| | 2026-02-27 | 문서 작성 | 통합 계획 Phase 2 상세 문서 작성 |