# 절곡 세부품목 → 자재투입 → LOT 매핑 통합 개발 계획 > **작성일**: 2026-02-21 > **목적**: 절곡 작업일지의 4대 제품 카테고리(가이드레일/하단마감재/셔터박스/연기차단재) 세부품목을 items 테이블과 연동하고, BOM 기반 자재투입 → LOT 추적 파이프라인 구축 > **기준 문서**: `5130/output/viewBendingWork_UA.php`, `api/app/Services/Production/BendingInfoBuilder.php`, `docs/dev_plans/bending-preproduction-stock-plan.md` > **상태**: 📋 분석 완료, 개발 계획 수립 중 --- ## 📍 현재 진행 상태 | 항목 | 내용 | |------|------| | **마지막 완료 작업** | LOT 추적 데이터 누락 분석 (7개 GAP 발견, 조치 계획 수립) | | **다음 작업** | GAP 1 즉시 수정 (registerMaterialInput 통일) → 방안 B 구현 | | **진행률** | 분석 완료, GAP 해결 및 개발 착수 전 | | **마지막 업데이트** | 2026-02-22 | --- ## 1. 개요 ### 1.1 배경 절곡 작업일지(WorkerScreen)에는 4대 제품 카테고리가 표시되며, 각 카테고리별 세부품목에 LOT 번호를 입력하여 자재를 투입해야 한다. ``` 작업일지 (절곡 WO202602210027) ├── 1. 가이드레일 (세부: 마감재, 본체, C형, D형, 하부BASE) ├── 2. 하단마감재 (세부: 하단마감재, 보강엘바, 보강평철, 별도마감) ├── 3. 셔터박스 (세부: 전면부, 린텔부, 점검구, 후면부, 상부덮개, 마구리) └── 4. 연기차단재 (세부: 레일용 W50, 케이스용 W80) ``` 현재 상태: - **구현 완료**: BendingInfoBuilder(bending_info 자동생성), Items Master(BD-XX-XX 품목 등록), getMaterials API, 자재투입/LOT 연동 API - **미구현(핵심 Gap)**: 세부품목이 items 테이블의 BOM으로 연결되지 않아 자재투입 시 세부품목별 LOT 매핑 불가 ### 1.2 핵심 문제 ``` 현재 흐름 (불완전): 견적 → bom_result에 부모 품목 저장 (BD-가이드레일-KSS01-SUS-120*70, qty=8.5m) → 작업지시 → BendingInfoBuilder가 길이 버킷팅 (4300mm×1, 4000mm×1) → work_order_items에 부모 품목 등록 → getMaterials() 호출 시 item.bom이 null → fallback: 부모 품목 자체를 자재로 표시 (1건) → 세부품목(BD-RS-43, BD-RM-40 등) LOT 매핑 불가 목표 흐름 (방안 B 채택): 견적 → bom_result에 부모 품목 저장 (기존 그대로, 수정 불필요) → 작업지시 생성 시 BendingInfoBuilder 확장: 길이 버킷팅 결과로 BD-XX-NN 세부품목 조회 → 동적 BOM 생성 → work_order_items.options.dynamic_bom에 세부품목 저장 → getMaterials()에서 dynamic_bom 우선 사용 → 각 세부품목별 StockLot 조회 → LOT 입력 → 자재투입 완료 ``` ### 1.3 성공 기준 | 기준 | 측정 방법 | |------|----------| | 작업일지의 4대 카테고리 세부품목이 items와 1:1 매핑 | 각 세부품목의 item_id 존재 확인 | | 자재투입 화면에서 세부품목별 LOT 입력 가능 | getMaterials API가 세부품목 리스트 반환 | | LOT 번호 입력 시 재고 차감 정상 동작 | stock_transactions 기록 확인 | | 레거시 5130과 동일한 LOT prefix 체계 유지 | LOT prefix 코드 일치 검증 | --- ## 2. 레거시 5130 절곡품 체계 분석 ### 2.1 제품코드 시스템 > **참고**: 제품코드는 작업일지 4대 카테고리(가이드레일/하단마감재/셔터박스/연기차단재)와 별개 개념. > 제품코드는 스크린/철재 × SUS/EGI 조합에 의한 **제품 모델 구분**이며, 각 모델별로 전개치수가 다르다. | 제품코드 | 마감재질 | 설명 | |---------|---------|------| | KSS01 | SUS 1.2T (기본) | 스크린 SUS | | KSS02 | SUS 1.2T | 스크린 SUS (변형) | | KSE01 | EGI 1.55T (기본) + 옵션 SUS | 스크린 EGI (표준) | | KWE01 | EGI 1.55T (기본) + 옵션 SUS | 스크린 EGI (광폭) | | KTE01 | EGI/SUS | 철재 | | KDSS01 | SUS | 디딤형 SUS | | KQTS01 | SUS | 특수형 | **마감재질 결정 로직** (`5130/output/viewBendingWork_UA.php:317-355`): ``` KSS01/KSS02 → GuidrailFinish = SUS 1.2T, bodyMaterial = EGI 1.55T KSE01/KWE01 + SUS마감 → GuidrailFinish = EGI 1.55T, GuidrailExtraFinish = SUS 1.2T KSE01/KWE01 + EGI마감 → GuidrailFinish = EGI 1.55T, GuidrailExtraFinish = EGI 1.55T ``` ### 2.2 LOT Prefix 전체 맵 #### 2.2.1 가이드레일 (Guide Rail) **벽면형 (Wall type, 412*350)** | 세부품목 | KSS01 prefix | KSE01/KWE01 EGI prefix | KSE01/KWE01 SUS prefix | |---------|-------------|----------------------|----------------------| | ①마감재 | RS | RE | RE | | ②본체 | RM | RM | RM | | ③C형 | RC | RC | RC | | ④D형 | RD | RD | RD | | ⑤별도마감 | - | - | YY | | 하부BASE | XX | XX | XX | **측면형 (Side type, 120*120)** | 세부품목 | KSS01 prefix | KSE01/KWE01 EGI prefix | KSE01/KWE01 SUS prefix | |---------|-------------|----------------------|----------------------| | ①②마감재 | SS | SE | SE | | ③본체 | SM | SM | SM | | ④본체디딤 | SC | SC | SC | | ⑤C형 | SD | SD | SD | | ⑥D형 | SM | SM | SM | | ⑦⑧별도마감 | - | - | YY | | 하부BASE | XX | XX | XX | #### 2.2.2 하단마감재 (Bottom Bar) | 세부품목 | EGI prefix | SUS prefix | 재질 | 전개치수 | |---------|-----------|-----------|------|---------| | ①하단마감재 | BE | BS | EGI 1.55T / SUS 1.2T | (60*40) | | ②보강엘바 | LA | LA | EGI 1.55T | (60*17) | | ③보강평철 | HH | HH | EGI 1.15T | - | | ④별도마감재 | YY | - | SUS 1.2T (SUS마감 시만) | - | **하단마감재 prefix 결정 로직** (`5130:718-721`): ```php if ($GuidrailFinish == 'EGI 1.55T') → $BTmat = 'BE'; else → $BTmat = 'BS'; ``` #### 2.2.3 셔터박스 (Shutter Box) **표준 사이즈 (500*380)** | 세부품목 | prefix | 치수 계산 | |---------|--------|----------| | ①전면부 | CF | boxheight + 122 | | ②린텔부 | CL | boxwidth - 330 | | ③점검구 | CP | boxwidth - 200 | | ④후면코너부/후면부 | CB | 170 또는 boxheight + 170 | | ⑥상부덮개 | XX | - | | ⑦마구리(측면부) | XX | - | **비표준 사이즈**: 모든 세부품목에 XX prefix 사용 #### 2.2.4 연기차단재 (Smoke Barrier) | 세부품목 | prefix | 재질 | |---------|--------|------| | 레일용 W50 | GI | EGI 0.8T + 화이바 글라스 코팅직물 | | 케이스용 W80 | GI | EGI 0.8T + 화이바 글라스 코팅직물 | ### 2.3 길이 코드 매핑 (getSLengthCode) | 길이(mm) | 코드 | 카테고리 | |---------|------|---------| | 1219 | 12 | 기타 | | 2438 | 24 | 기타 | | 3000 | 30 | 기타 | | 3500 | 35 | 기타 | | 4000 | 40 | 기타 | | 4150 | 41 | 기타 | | 4200 | 42 | 기타 | | 4300 | 43 | 기타 | | 3000 | 53 | 연기차단재50 | | 4000 | 54 | 연기차단재50 | | 3000 | 83 | 연기차단재80 | | 4000 | 84 | 연기차단재80 | ### 2.4 동적 품목코드 생성 규칙 5130에서 LOT 입력 시 사용되는 `data-itemname` 속성: ``` [PREFIX]-[LENGTH_CODE] 예시: RS-40 = 가이드레일 벽면형 SUS 마감재 4000mm RM-35 = 가이드레일 본체 3500mm BE-30 = 하단마감재 EGI 3000mm CF-24 = 셔터박스 전면부 2438mm GI-53 = 연기차단재 W50 3000mm ``` **핵심**: 품목코드가 **길이에 따라 동적으로 결정**됨. 같은 "마감재"라도 3000mm면 `RS-30`, 4000mm면 `RS-40`이 된다. --- ## 3. SAM 현재 구현 현황 ### 3.1 구현 완료 | 기능 | 위치 | 설명 | |------|------|------| | BendingInfoBuilder | `api/app/Services/Production/BendingInfoBuilder.php` | 수주→작업지시 시 bending_info JSON 자동생성 | | categorizeBomItem() | 위 파일 :96-130 | BOM 아이템을 8개 카테고리로 분류 | | Items Master (BD-*) | items 테이블 (로컬+dev) | 절곡 품목 148개 (제품 마스터형 58 + LOT prefix형 90) | | getMaterials API | `WorkOrderService.php:1183` | work_order_items 순회 → item.bom 확인 → StockLot 조회 | | getMaterialsForItem API | `WorkOrderService.php:2678` | 개별 품목 자재 조회 | | registerMaterialInput | `react/.../WorkerScreen/actions.ts:288` | 자재투입 등록 POST API | | increaseFromProduction | `api/app/Services/StockService.php` | 생산완료 → 재고입고 | | 선생산 재고 흐름 | `docs/dev_plans/bending-preproduction-stock-plan.md` | Phase 1-3 완료 | ### 3.2 BD-* 품목 현황 (로컬 DB 확인 완료) **총 148개** BD-* 품목 (2026-02-21 확인): **A. 제품 마스터형 (58개)** — 부모 품목 (제품코드+재질+전개치수) ``` BD-가이드레일-KSS01-SUS-120*70 (20개: KSS01/KSS02/KSE01/KWE01/KTE01/KDSS01/KQTS01별) BD-하단마감재-KSE01-EGI-60*40 (10개) BD-케이스-500*380 (10개: 사이즈별) BD-마구리-505*355 (10개: 사이즈별) BD-L-BAR-KSS01-17*60 (5개) BD-보강평철-50 (1개) BD-가이드레일용 연기차단재 (1개) BD-케이스용 연기차단재 (1개) ``` **B. LOT prefix형 (90개)** — 자재투입 대상 세부품목 (길이별) | prefix | 개수 | 설명 | |--------|------|------| | BD-RS | 5 | 가이드레일(벽면) SUS 마감재 | | BD-RM | 6 | 가이드레일(벽면) 본체 | | BD-RC | 6 | 가이드레일(벽면) C형 | | BD-RD | 6 | 가이드레일(벽면) D형 | | BD-RT | 2 | 가이드레일(벽면) 본체(철재) | | BD-SS | 4 | 가이드레일(측면) SUS 마감재 | | BD-SM | 5 | 가이드레일(측면) 본체/D형 | | BD-SC | 5 | 가이드레일(측면) C형 | | BD-SD | 5 | 가이드레일(측면) D형 | | BD-ST | 1 | 가이드레일(측면) 본체(철재) | | BD-SU | 4 | 가이드레일(측면) SUS2 (별도마감) | | BD-BE | 2 | 하단마감재(스크린) EGI | | BD-BS | 5 | 하단마감재(스크린) SUS | | BD-TS | 1 | 하단마감재(철재) SUS | | BD-LA | 2 | L-Bar 스크린용 | | BD-CF | 6 | 케이스 전면부 | | BD-CL | 6 | 케이스 린텔부 | | BD-CP | 6 | 케이스 점검구 | | BD-CB | 6 | 케이스 후면코너부 | | BD-GI | 7 | 연기차단재 화이바원단 | > XX(하부BASE), YY(별도SUS마감), HH(보강평철)은 미등록 → 방안 B 구현 전 BD-XX-NN, BD-YY-NN, BD-HH-NN 형태로 등록 예정 ### 3.3 미구현 Gap → 해결 방향 > **방안 B 확정(섹션 4) 및 LOT GAP 분석(섹션 7)으로 모두 해결 방향 확정됨.** | Gap | 해결 방향 | 참조 | |-----|----------|------| | items.bom 연결 (bom = null) | dynamic_bom으로 대체 (items.bom 수정 불필요) | 섹션 4.4, 4.5 | | 가변 세부품목 배정 | BendingInfoBuilder 확장으로 길이별 동적 품목 결정 | 섹션 4.3 | | order_items 세부품목 | bom_result 기반으로 BendingInfoBuilder가 직접 생성, order_items 수정 불필요 | 섹션 4.3 | | LOT prefix 매핑 | dynamic_bom JSON에 lot_prefix 필드 포함 | 섹션 4.4 | | XX/YY/HH 미등록 품목 | BD-XX-NN, BD-YY-NN, BD-HH-NN 형태로 items에 등록 예정 | 섹션 3.2 | --- ## 4. 아키텍처 설계 (방안 B 확정) ### 4.1 방안 선택 근거 **방안 B (작업지시 시 동적 BOM 생성)** 채택. | 근거 | 설명 | |------|------| | 견적 금액과 무관 | 견적은 "부모 품목 × 총길이(m) × 단가"로 계산. 세부품목은 금액에 영향 없음 | | 길이 버킷팅 이미 구현됨 | BendingInfoBuilder에 `heightLengthData()`, `bottomBarDistribution()`, `shutterBoxDistribution()` 존재 | | 수정 범위 최소 | BendingInfoBuilder에 BD-XX-NN 조회 로직만 추가. 견적 로직 수정 불필요 | | bom_result 일관성 유지 | 견적 결과(bom_result)를 변경하지 않고, 그 위에 세부 매핑만 추가 | > **참고**: 견적과 작업지시는 동일한 BOM 산출 결과(`order_nodes.options.bom_result`)를 공유한다. 견적 계산과 자재투입은 같은 기준을 사용해야 일관성 유지. ### 4.2 bom_result 실제 데이터 구조 (DB 확인 완료) 견적 시 `order_nodes.options.bom_result.items`에 저장되는 절곡 관련 부모 품목: ``` BD-가이드레일-KSS01-SUS-120*70 qty=8.5m ← 부모 품목 (전개치수 기준) BD-케이스-500*380 qty=3.22m BD-마구리-505*385 qty=1 00035 (하장바) qty=3 BD-L-BAR-KSS01-17*60 qty=3.22m BD-보강평철-50 qty=3.22m EST-SMOKE-레일용 qty=8.5 EST-SMOKE-케이스용 qty=3.22 ``` 이 부모 품목들은 **길이별 세부품목(BD-RS-40 등)으로 분해**되어야 자재투입이 가능. ### 4.3 동적 BOM 생성 흐름 ``` [견적] (기존 그대로, 수정 불필요) QuoteCalculationService.calculateBom() → bom_result: { BD-가이드레일-KSS01-SUS-120*70, qty=8.5m, ... } → order_nodes.options.bom_result에 저장 ↓ [수주 확정 → 작업지시 생성] BendingInfoBuilder.build() ← 확장 대상 ① bom_result에서 부모 품목 읽기 (기존) ② 치수별 길이 버킷팅 (기존: heightLengthData 등) 예: 8.5m → 4300mm×1개 + 4000mm×1개 ③ [신규] 길이코드 + LOT prefix → BD-XX-NN 품목 조회 예: 4300mm → 코드43, 마감재 RS → BD-RS-43 (item_id 조회) ④ [신규] dynamic_bom 생성 → work_order_items.options에 저장 ↓ [자재투입] getMaterials(workOrderId) ← 소폭 수정 → work_order_items 순회 → [수정] options.dynamic_bom이 있으면 우선 사용 → 없으면 기존 item.bom fallback → 각 세부품목(BD-RS-43 등)의 StockLot 조회 ↓ [자재투입 등록] registerMaterialInput() (기존 그대로) → stock_transactions 기록 → stock_lots 차감 ``` ### 4.4 dynamic_bom JSON 구조 (work_order_items.options) ```json { "dynamic_bom": [ { "child_item_id": 15812, "child_item_code": "BD-RS-43", "lot_prefix": "RS", "part_type": "마감재", "category": "guideRail", "material_type": "SUS", "length_mm": 4300, "qty": 1 }, { "child_item_id": 15809, "child_item_code": "BD-RS-40", "lot_prefix": "RS", "part_type": "마감재", "category": "guideRail", "material_type": "SUS", "length_mm": 4000, "qty": 1 }, { "child_item_id": 15826, "child_item_code": "BD-RM-43", "lot_prefix": "RM", "part_type": "본체", "category": "guideRail", "material_type": "EGI", "length_mm": 4300, "qty": 1 } ] } ``` ### 4.5 getMaterials() 수정 범위 `WorkOrderService.php:1198-1238`에서 기존 `item.bom` 체크 앞에 `dynamic_bom` 체크 추가: ``` foreach (work_order_items as woItem): // [신규] dynamic_bom 우선 체크 dynamicBom = woItem.options.dynamic_bom ?? null if (dynamicBom is not empty): foreach (dynamicBom as bomItem): childItem = Item::find(bomItem.child_item_id) materialItems[] = {item: childItem, bom_qty: bomItem.qty, ...} // [기존] items.bom fallback elseif (item.bom is not empty): ... 기존 로직 ... // [기존] 최종 fallback: 품목 자체를 자재로 else: ... ``` --- ## 5. LOT Prefix → BD 코드 대응 관계 (실제 DB 확인) | LOT Prefix | 5130 세부품목 | SAM 품목코드 패턴 | 등록 수 | 카테고리 | |-----------|-------------|-----------------|:------:|---------| | RS | 벽면형 SUS 마감재 | BD-RS-[길이코드] | 5 | 가이드레일 | | RM | 벽면형 본체 | BD-RM-[길이코드] | 6 | 가이드레일 | | RC | 벽면형 C형 | BD-RC-[길이코드] | 6 | 가이드레일 | | RD | 벽면형 D형 | BD-RD-[길이코드] | 6 | 가이드레일 | | RT | 벽면형 본체(철재) | BD-RT-[길이코드] | 2 | 가이드레일 | | SS | 측면형 SUS 마감재 | BD-SS-[길이코드] | 4 | 가이드레일 | | SM | 측면형 본체/D형 | BD-SM-[길이코드] | 5 | 가이드레일 | | SC | 측면형 C형 | BD-SC-[길이코드] | 5 | 가이드레일 | | SD | 측면형 D형 | BD-SD-[길이코드] | 5 | 가이드레일 | | ST | 측면형 본체(철재) | BD-ST-[길이코드] | 1 | 가이드레일 | | SU | 측면형 SUS2 (별도마감) | BD-SU-[길이코드] | 4 | 가이드레일 | | BE | 하단마감재(스크린) EGI | BD-BE-[길이코드] | 2 | 하단마감재 | | BS | 하단마감재(스크린) SUS | BD-BS-[길이코드] | 5 | 하단마감재 | | TS | 하단마감재(철재) SUS | BD-TS-[길이코드] | 1 | 하단마감재 | | LA | L-Bar 스크린용 | BD-LA-[길이코드] | 2 | 하단마감재 | | CF | 케이스 전면부 | BD-CF-[길이코드] | 6 | 셔터박스 | | CL | 케이스 린텔부 | BD-CL-[길이코드] | 6 | 셔터박스 | | CP | 케이스 점검구 | BD-CP-[길이코드] | 6 | 셔터박스 | | CB | 케이스 후면코너부 | BD-CB-[길이코드] | 6 | 셔터박스 | | GI | 연기차단재 화이바원단 | BD-GI-[길이코드] | 7 | 연기차단재 | --- ## 6. 프론트엔드 매핑 검토 결과 ### 6.1 작업일지 세부품명 → BD-* 매핑: **가능 ✅** 각 세부품목에 `lotPrefix` 필드가 이미 정의되어 있다. | 섹션 | LOT Prefix (utils.ts 하드코딩) | BD-* 매핑 예시 | |------|-------------------------------|---------------| | 가이드레일(벽면) | RS, RT, RC, RD, XX(하부BASE) | `BD-RS-40`, `BD-RT-43` | | 가이드레일(측면) | SS, ST, SC, SD, XX(하부BASE) | `BD-SS-40`, `BD-ST-43` | | 하단바 | BE, BS, LA | `BD-BE-40`, `BD-BS-35` | | 셔터박스 | CF, CL, CP, CB | `BD-CF-40`, `BD-CL-35` | | 방연 | GI | `BD-GI-53`, `BD-GI-83` | **매핑 공식**: `lotPrefix` + `getSLengthCode(길이mm)` → `BD-{prefix}-{lengthCode}` → items 테이블 code 컬럼 **현재 한계**: LOT NO 컬럼이 `"-"`으로 하드코딩 → `dynamic_bom` 연동 후 실제 LOT 번호 표시 가능 **프론트 수정 범위**: 소규모 ### 6.2 자재투입 모달 세부품목 선택: **현재 불가 ❌ → 수정 필요** | 항목 | 현재 상태 | 방안 B 적용 후 | |------|----------|--------------| | 자재 그룹핑 | 부모 품목 단위 | 세부품목(BD-RS-40 등) 단위 | | LOT 선택 | 부모 품목의 StockLot만 표시 | 세부품목의 StockLot 표시 | | FIFO 배분 | 품목 단위 | 세부품목 단위 | **핵심**: 백엔드 `getMaterials()` 수정(섹션 4.5)이 완료되면 응답에 세부품목이 포함되므로, 프론트 모달은 **기존 렌더링 로직 그대로** 세부품목을 표시할 수 있다. **프론트 수정 범위**: 중규모 — 그룹 헤더에 세부품목명 표시, 선택적 UX 개선 ### 6.3 종합 연결 흐름 ``` 작업일지 세부품명 ──── lotPrefix + lengthCode ────→ BD-XX-NN (items 테이블) │ │ ▼ ▼ LOT NO 표시 ◄──── dynamic_bom ────────────────── getMaterials() │ │ ▼ ▼ 자재투입 모달 ◄──── 세부품목 단위 LOT 선택 ────── FIFO 배분 ``` **구현 순서**: BendingInfoBuilder 확장(dynamic_bom 생성) → getMaterials() 수정 → 프론트 모달 수정 → 작업일지 LOT NO 표시 --- ## 7. LOT 추적 데이터 누락 분석 (2026-02-22) ### 7.1 현재 LOT 추적 인프라 ``` 수주(orders) ──FK──→ 작업지시(work_orders) ──FK──→ 산출물 LOT(stock_lots) │ │ │ │ source_order_item_id │ work_order_material_inputs│ work_order_id ▼ ▼ ▼ order_items ←── work_order_items ──→ 투입 LOT(stock_lots) ──→ stock_transactions ``` | 연결 | FK/테이블 | 상태 | |------|----------|:----:| | 수주 → 작업지시 | `work_orders.sales_order_id` | ✅ | | 수주품목 → 작업지시품목 | `work_order_items.source_order_item_id` | ✅ | | 생산완료 → 산출물 LOT | `stock_lots.work_order_id` | ✅ | | 구매입고 → 원자재 LOT | `stock_lots.receiving_id` | ✅ | | 자재투입 이력 | `work_order_material_inputs` | ✅ | | 거래 이력 | `stock_transactions` | ✅ | ### 7.2 발견된 GAP #### 🔴 GAP 1: `registerMaterialInput()`에서 투입 이력 레코드 미생성 **위치**: `WorkOrderService.php` L1330-1390 ``` registerMaterialInput() (L1330) ← 작업지시 전체 단위 → 재고 차감 ✅, 감사 로그 ✅, WorkOrderMaterialInput 레코드 ❌ registerMaterialInputForItem() (L2821) ← 개소(품목) 단위 → 재고 차감 ✅, 감사 로그 ✅, WorkOrderMaterialInput 레코드 ✅ ``` **해결**: `registerMaterialInputForItem()`으로 API 통일 **우선순위**: 🔴 즉시 (방안 B와 독립적으로 수정 가능) #### 🔴 GAP 2: dynamic_bom 미구현 → 절곡 세부품목 LOT 추적 불가 현재 `items.bom`만 체크 → 절곡 부모 품목의 bom이 null → 세부품목이 자재 목록에 미포함. **해결**: 방안 B 구현 (섹션 4.5) **우선순위**: 🔴 방안 B와 동시 #### 🔴 GAP 5: bending_info ↔ dynamic_bom 정합성 보장 메커니즘 없음 별도 생성 시 작업일지 표시 ≠ 자재투입 대상 불일치 위험. **해결**: BendingInfoBuilder에서 **동시에 생성**하여 같은 길이 버킷팅 결과 공유 **우선순위**: 🔴 방안 B와 동시 (설계 시 반영 필수) #### 🔴 GAP 4: 수주 연결 작업지시 산출물이 stock_lots 안 거침 **위치**: `WorkOrderService.php` L576-583 (`updateStatus()`) ```php if ($workOrder->sales_order_id) { $this->createShipmentFromWorkOrder(...); // 출하 직행, stock_lots 미거침 } else { $this->stockInFromProduction($workOrder); // 재고 입고 → LOT 생성 } ``` **원인**: 출하 시스템이 아직 러프하게 구성된 상태 (의도된 설계 아님) **해결 (권장)**: **"생산완료 → 항상 재고 입고(stock_lots)" 통일** | 항목 | 현재 | 권장 변경 | |------|------|----------| | 선생산 완료 | `stockInFromProduction()` → stock_lots ✅ | 변경 없음 | | 수주 연결 완료 | `createShipmentFromWorkOrder()` → 출하 직행 | `stockInFromProduction()` → stock_lots 생성 → 출하는 별도 프로세스 | **우선순위**: 🔴 출하 시스템 설계 시 함께 해결 #### 🟡 GAP 3: 투입 LOT → 산출 LOT 직접 연결 없음 간접 추적 가능 (`산출 LOT → work_order_id → material_inputs → 투입 LOT`). 직접 연결 테이블(`lot_genealogy`)은 향후 고도화. #### 🟢 GAP 6, 7 - **GAP 6**: 불량 LOT 별도 관리 없음 → 품질 관리 고도화 시 - **GAP 7**: 공정 간 반제품 LOT 연결 → 기존 `registerMaterialInputForItem()` 구조로 충분 ### 7.3 우선순위별 조치 계획 | 우선순위 | GAP | 조치 | 시점 | |:--------:|-----|------|------| | 🔴 | #1 registerMaterialInput 이력 미기록 | `registerMaterialInputForItem()`으로 API 통일 | 즉시 | | 🔴 | #2 dynamic_bom 미구현 | getMaterials()에 dynamic_bom 우선 체크 | 방안 B 동시 | | 🔴 | #5 bending_info ↔ dynamic_bom 정합성 | BendingInfoBuilder에서 동시 생성 | 방안 B 동시 | | 🔴 | #4 수주 연결 산출물 LOT 미생성 | 생산완료 → 항상 stock_lots 입고 통일 | 출하 시스템 설계 시 | | 🟡 | #3 투입↔산출 LOT 직접 연결 | lot_genealogy 테이블 고려 | 향후 고도화 | ### 7.4 방안 B 적용 후 목표 LOT 추적 체인 ``` [수주] orders └─ order_nodes.options.bom_result (부모 품목 + 총길이) │ ▼ source_order_item_id [작업지시] work_orders + work_order_items ├─ options.bending_info (작업일지 표시) ─┐ └─ options.dynamic_bom (세부품목 매핑) ─┤ 같은 BendingInfoBuilder에서 동시 생성 │ └─ 정합성 자동 보장 ▼ getMaterials() → dynamic_bom 우선 체크 [자재투입] work_order_material_inputs ├─ work_order_item_id (부모 품목 개소) ├─ item_id = BD-RS-43 (세부품목) └─ stock_lot_id = LOT-XXXX (투입 LOT) │ ▼ 재고 차감 (stock_transactions: OUT, work_order_input) [생산완료] stock_lots (work_order_id = 작업지시 ID) ├─ 선생산: stock_lots 생성 ✅ (현재 동작) └─ 수주 연결: stock_lots 생성 ✅ (GAP 4 해결 후) │ ▼ 역추적 산출물 LOT → work_order → material_inputs → 투입 LOT → receiving → 공급업체 ``` --- ## 8. 개발 영향 분석 및 위험 평가 (2026-02-22) ### 8.1 과제별 효과 및 위험 #### 과제 1: registerMaterialInput() API 통일 (GAP #1) **효과**: 자재투입 이력이 `work_order_material_inputs`에 빠짐없이 기록 → 역추적 체인 완성 **위험**: - 기존 `registerMaterialInput()`은 `work_order_item_id` 파라미터 미수신 → 프론트에서 해당 값 전달하도록 수정 필요 - L2860-2861 `StockLot::find()` → `$lot->stock->item_id` 역추적 시 Eager Loading 없으면 N+1 쿼리 #### 과제 2: BendingInfoBuilder 확장 — dynamic_bom 생성 (GAP #2, #5) **효과**: 견적 로직 수정 없이 세부품목별 LOT 추적 가능. bending_info와 동시 생성으로 정합성 보장. **위험**: | 위험 | 상세 | 대응 | |------|------|------| | items 미매칭 | `bucketToStandardLength()`가 표준 길이 초과 시 원본 반환(L862-864) → `BD-RS-4500` 같은 비표준 코드 생성 | 아이템 미발견 시 fallback + 경고 로그 | | prefix 결정 복잡성 | KSS01→RS, KSE01→RE. SUS마감 여부로 YY 포함. 벽면/측면 prefix 세트 상이 | **PrefixResolver 클래스 분리** (하드코딩 지양) | | 혼합형 가이드레일 | `buildGuideRail()`에서 wall+side 동시 생성 시 prefix 분기 복잡 | 벽면/측면 각각 독립 dynamic_bom 생성 | | 생성 이후 수정 | 치수/품목 변경 시 bending_info + dynamic_bom 동시 재생성 필요 | 업데이트 메커니즘 설계 | | JSON 검증 부재 | dynamic_bom은 JSON → DB 레벨 제약 없음 | Application 레벨 DTO/Validator | #### 과제 3: getMaterials() 수정 — dynamic_bom 우선 체크 **효과**: 프론트 MaterialInputModal이 세부품목 단위로 LOT 선택 가능 **위험**: - **N+1 쿼리 누적**: 현재 getMaterials() 자체가 N+1 다수. dynamic_bom 추가 시 세부품목 15-25개만큼 쿼리 추가(총 30-50회). `Item::whereIn()` 배치 조회로 개선 필수 - **uniqueMaterials 합산 시 정보 소실**: L1240-1248에서 같은 item_id면 required_qty 합산 → 어느 `work_order_item`에 속하는지 소실. `registerMaterialInputForItem()` 호출 시 `work_order_item_id` 지정 어려움 → 합산 단위를 `(item_id, work_order_item_id)` 쌍으로 변경 권장 #### 과제 4: 수주 연결 산출물 LOT 생성 (GAP #4) **효과**: 모든 생산 완료 건에 stock_lots 기록 → 완전한 LOT 추적 체인 **위험**: - **출하 시스템 의존성**: `createShipmentFromWorkOrder()` 단순 제거 시 현재 출하 흐름 깨짐 → 출하 재설계와 병행 필수 - **재고 이중 계상**: stock_lots 입고~출하 시간 차 동안 재고로 잡힘 → 다른 주문에 배정될 위험 ### 8.2 Race Condition 분석 | 시나리오 | 리스크 | 대응 | |---------|-------|------| | 자재투입 동시 요청 | 두 작업자가 같은 LOT 동시 차감 → 초과 차감 | `lockForUpdate()` 비관적 잠금 | | getMaterials→투입 시간 차 | 조회 후 다른 작업지시에서 같은 LOT 소진 | 투입 시 available_qty 재검증 (decreaseFromLot에서 수행), 부족 시 명확한 오류 | ### 8.3 마이그레이션/롤백 평가 | 항목 | 평가 | |------|------| | DB 스키마 변경 | **없음** — 기존 options JSON 컬럼 활용 | | 코드 롤백 | Git 롤백으로 복원 가능 | | 데이터 롤백 | dynamic_bom이 있는 건도 코드 롤백 시 기존 fallback 동작 → **하위 호환성 확보** | | items 마스터 롤백 | dynamic_bom의 child_item_id가 참조 가능 → 주의 | ### 8.4 개선 권장사항 | 영역 | 제안 | 시점 | |------|------|------| | 쿼리 최적화 | getMaterials() 내 `whereIn()` 배치 조회 + Eager Loading | 방안 B 구현 시 | | Prefix 매핑 | BendingInfoBuilder 하드코딩 대신 **PrefixResolver 클래스** 분리 | 방안 B 구현 시 | | 검증 레이어 | dynamic_bom JSON DTO/Validator 클래스 | 방안 B 구현 시 | | 마스터 데이터 검증 | prefix × lengthCode 전체 조합 items 존재 확인 스크립트 | 방안 B 구현 전 | | 아이템 미발견 처리 | 로그 경고 + 관리자 알림 + graceful fallback | 방안 B 구현 시 | | dynamic_bom 메타정보 | 생성 시각/빌더 버전을 options에 포함 → 디버깅 용이 | 방안 B 구현 시 | | 테스트 | productCode × guideType 전 조합 단위 테스트 + getMaterials→투입 통합 테스트 | 방안 B 구현 후 | ### 8.5 종합 평가 **방안 B는 기술적으로 타당.** 견적 로직 미변경, 기존 JSON options 패턴 활용, 하위 호환성 유지. **핵심 리스크 2가지**: 1. **items 마스터 데이터 완전성** — 19종 prefix × 7-12개 길이코드 조합이 items에 정확히 존재해야 함 2. **LOT prefix 결정 로직의 복잡성** — 제품코드/마감재질/가이드타입에 따른 분기 다수 → 하드코딩 시 유지보수 어려움 → **마스터 데이터 검증 스크립트**와 **PrefixResolver 분리**를 개발 초기에 확보할 것 --- ## 9. 참고 문서 | 문서 | 경로 | |------|------| | 선생산 재고 계획 | `docs/dev_plans/bending-preproduction-stock-plan.md` | | BendingInfoBuilder | `api/app/Services/Production/BendingInfoBuilder.php` | | QuoteCalculationService | `api/app/Services/Quote/QuoteCalculationService.php` | | FormulaEvaluatorService | `api/app/Services/Quote/FormulaEvaluatorService.php` | | EstimatePriceService | `api/app/Services/Quote/EstimatePriceService.php` | | WorkOrderService | `api/app/Services/WorkOrderService.php` | | StockService | `api/app/Services/StockService.php` | | WorkOrderMaterialInput 모델 | `api/app/Models/Production/WorkOrderMaterialInput.php` | | 자재투입 마이그레이션 | `api/database/migrations/2026_02_12_100000_create_work_order_material_inputs_table.php` | | stock_lots work_order_id FK | `api/database/migrations/2026_02_21_100000_add_work_order_id_to_stock_lots_table.php` | | MaterialInputModal | `react/src/components/production/WorkerScreen/MaterialInputModal.tsx` | | 5130 작업일지 | `5130/output/viewBendingWork_UA.php` | | Bending types/utils | `react/src/components/production/WorkOrders/documents/bending/` | --- ## 10. 변경 이력 | 날짜 | 변경 내용 | |------|----------| | 2026-02-21 | 문서 초안 작성 (현황 분석, 5130 체계 정리) | | 2026-02-21 | 로컬 DB BD-* 148개 확인, 제품코드 7종 추가, 추가 prefix(RT/ST/SU/TS) 발견 | | 2026-02-21 | **방안 B 확정**: 작업지시 시 BendingInfoBuilder 확장으로 동적 BOM 생성 | | 2026-02-21 | 프론트엔드 매핑 검토 추가 (lotPrefix→BD-* 매핑 가능, 자재투입 모달 수정 필요) | | 2026-02-22 | LOT 추적 데이터 누락 분석: 7개 GAP 발견, 우선순위별 조치 계획 수립 | | 2026-02-22 | 문서 정리: 중복/해소 항목 제거, dynamic_bom에 category/material_type 추가 | | 2026-02-22 | 섹션 8 추가: 개발 영향 분석 및 위험 평가 (과제별 효과/위험, race condition, 롤백, 개선 권장) |