# BOM 아이템 ↔ Items Master 매핑 작업 계획 > **작성일**: 2026-02-05 > **목적**: BOM 산출 로직에서 생성하는 모든 아이템에 SAM items master의 item_code/item_id를 매핑하여, 수주 등록 시 코드 기반 아이템 관리가 가능하도록 함 > **상태**: ✅ Phase 1, 2 완료 (검증 대기) --- ## 📍 현재 진행 상태 | 항목 | 내용 | |------|------| | **마지막 완료 작업** | Phase 2: BOM 산출 로직 매핑 수정 완료 | | **다음 작업** | Phase 3: 수주 등록 화면에서 검증 | | **진행률** | 2/3 Phase (66%) | | **마지막 업데이트** | 2026-02-05 | --- ## 1. 개요 ### 1.1 문제 상황 ``` 현재 상태: - KyungdongFormulaHandler에서 22종 아이템 생성 - 그 중 5종은 item_code/item_id 없이 이름만으로 생성됨: 1. 케이스 마구리 (calculateSteelItems) 2. L바 (calculateSteelItems) 3. 무게평철12T (calculateSteelItems) 4. 검사비 (calculateDynamicItems) ← 유일한 SAM 미등록 아이템 5. 주자재(스크린/슬랫) (calculateDynamicItems) ← KD-* 코드 사용 문제점: - 수주 등록 시 아이템 그룹핑/집계에서 코드 기반 매칭 불가 - item_code가 없으면 item_name으로만 집계되어 중복 발생 ``` ### 1.2 목표 상태 ``` 목표: - BOM 산출 결과의 모든 22종 아이템에 item_code + item_id 필수 - 수주 등록 시 동일 item_code 기준으로 수량/금액 집계 기대 효과: - 3개소에 "환봉" 각 1개씩 → item_code "90201" 기준 3개로 집계 ``` ### 1.3 핵심 원칙 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 🎯 핵심 원칙 (CRITICAL) │ ├─────────────────────────────────────────────────────────────────┤ │ 1. items master에 등록된 제품을 매핑해야 함 │ │ → 코드를 임의로 만들어내면 안됨 │ │ 2. 기존 EST-/BD-/PT- 코드 체계를 활용 │ │ 3. BOM 산출 결과의 모든 아이템에 item_code + item_id 필수 │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.4 변경 승인 정책 | 분류 | 예시 | 승인 | |------|------|------| | ✅ 즉시 가능 | BOM 로직 내 item_code/item_id 매핑 추가 | 불필요 | | ⚠️ 컨펌 필요 | items master에 신규 아이템 등록 | **필수** | | 🔴 금지 | items 테이블 구조 변경, 기존 코드 체계 변경 | 별도 협의 | --- ## 2. 전체 아이템 매핑 테이블 (22종) ### 2.1 calculateSteelItems (절곡품) - 10종 | # | BOM 아이템명 | 현재 item_code | SAM 매핑 코드 | SAM item_id | 매핑 방법 | |---|-------------|---------------|--------------|-------------|----------| | 1 | 케이스 | null | `BD-케이스-{규격}` | lookup | 규격으로 items 검색 (예: BD-케이스-500*350) | | 2 | 케이스용 연기차단재 | null | `EST-SMOKE-케이스용` | 14912 | 고정 매핑 | | 3 | **케이스 마구리** | **null** | `BD-마구리-{규격}` | lookup | 규격으로 items 검색 (예: BD-마구리-505*355) | | 4 | 가이드레일 | null | `BD-가이드레일-{모델}-{재질}-{규격}` | lookup | 모델+재질+규격으로 검색 | | 5 | 레일용 연기차단재 | null | `EST-SMOKE-레일용` | 14911 | 고정 매핑 | | 6 | 하장바 | null | `00035` 또는 `00036` | 14158/14159 | 재질에 따라 분기 | | 7 | **L바** | **null** | `BD-L-BAR-{모델}-{규격}` | lookup | 모델+규격으로 검색 (예: BD-L-BAR-KWE01-17*60) | | 8 | 보강평철 | null | `BD-보강평철-50` | 14790 | 고정 매핑 | | 9 | **무게평철12T** | **null** | `00021` | 14147 | 고정 매핑 (평철12T와 동일) | | 10 | 환봉 | null | `90201`~`90204` | 14407~14410 | 파이 규격에 따라 분기 (30/35/45/50파이) | ### 2.2 calculatePartItems (부자재) - 5종 | # | BOM 아이템명 | 현재 item_code | SAM 매핑 코드 | SAM item_id | 매핑 방법 | |---|-------------|---------------|--------------|-------------|----------| | 11 | 감기샤프트 | null | `EST-SHAFT-{인치}-{길이}` | lookup | 인치+길이로 검색 (예: EST-SHAFT-4-6) | | 12 | 각파이프 | null | `EST-PIPE-{두께}-{길이}` | lookup | 두께+길이로 검색 (예: EST-PIPE-1.4-6000) | | 13 | 모터받침 앵글 | null | `EST-ANGLE-BRACKET-{타입}` | lookup | 타입으로 검색 (스크린용/철제300K/400K/800K) | | 14 | 앵글 | null | `EST-ANGLE-MAIN-{타입}` | lookup | 앵글타입+길이로 검색 | | 15 | 조인트바 | null | `800361` | 14733 | 고정 매핑 | ### 2.3 calculateDynamicItems (동적항목) - 7종 | # | BOM 아이템명 | 현재 item_code | SAM 매핑 코드 | SAM item_id | 매핑 방법 | |---|-------------|---------------|--------------|-------------|----------| | 16 | **검사비** | **KD-INSPECTION** | `EST-INSPECTION` | **(신규등록)** | **items master 신규 등록 필요** | | 17 | 주자재(스크린) | KD-SCREEN | `EST-RAW-스크린-{타입}` | lookup | 타입으로 검색 (실리카/화이바/와이어) | | 18 | 주자재(슬랫) | KD-SLAT | `EST-RAW-슬랫-{타입}` | lookup | 타입으로 검색 (방범/방화) | | 19 | 모터 | KD-MOTOR-{용량} | `EST-MOTOR-{전압}-{용량}` | lookup | 전압+용량으로 검색 | | 20 | 제어기 | KD-CTRL-{타입} | `EST-CTRL-{타입}` | lookup | 타입으로 검색 (노출형/매립형) | | 21 | 뒷박스 | KD-CTRL-BACKBOX | `EST-CTRL-뒷박스` | 14863 | 고정 매핑 | | 22 | 브라켓 | (모터에 포함) | `KD브라켓트*` 또는 `EST-*` | lookup | 모터 용량에 따라 분기 | > **굵은 글씨**: 현재 미매핑 상태 (작업 대상) --- ## 3. 대상 범위 ### 3.1 Phase 1: 미등록 아이템 등록 (사용자 승인 필요) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1.1 | 검사비(EST-INSPECTION) items master 신규 등록 | ✅ | ID: 14913 | | 1.2 | 무게평철12T → 00021(평철12T) 동일 아이템 확인 | ✅ | ID: 14147 확인 완료 | ### 3.2 Phase 2: BOM 산출 로직 매핑 수정 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2.1 | calculateSteelItems: 10종 아이템에 item_code/item_id 매핑 | ✅ | withItemMapping 헬퍼 사용 | | 2.2 | calculatePartItems: 5종 아이템에 item_code/item_id 매핑 | ✅ | withItemMapping 헬퍼 사용 | | 2.3 | calculateDynamicItems: KD-* → EST-* 코드 변환 | ✅ | 모터/제어기/주자재 매핑 완료 | ### 3.3 Phase 3: 검증 | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 3.1 | 견적 산출 실행 → 모든 아이템 item_code/item_id 확인 | ✅ | 18종 아이템 모두 매핑됨 | | 3.2 | 수주 등록 → 코드 기반 아이템 그룹핑/집계 정상 동작 | ⏳ | 화면 검증 필요 | --- ## 4. 실행 환경 및 명령어 ### 4.1 Docker 환경 ```bash # API 컨테이너 접속 docker exec -it sam-api-1 bash # PHP Tinker 실행 docker exec sam-api-1 php artisan tinker --execute='...' ``` ### 4.2 주요 확인 명령어 ```bash # SAM items master에서 특정 코드 검색 docker exec sam-api-1 php artisan tinker --execute=' $item = \App\Models\Items\Item::where("tenant_id", 287) ->where("code", "EST-INSPECTION") ->first(); echo $item ? "ID: {$item->id}, Code: {$item->code}, Name: {$item->name}" : "NOT FOUND"; ' # 코드 패턴으로 검색 (BD-*, EST-* 등) docker exec sam-api-1 php artisan tinker --execute=' $items = \App\Models\Items\Item::where("tenant_id", 287) ->where("code", "like", "BD-마구리%") ->get(["id", "code", "name"]); foreach ($items as $item) { echo "{$item->id} | {$item->code} | {$item->name}" . PHP_EOL; } ' # 5130 chandj DB 연결 테스트 docker exec sam-api-1 php artisan tinker --execute=' $count = \Illuminate\Support\Facades\DB::connection("chandj") ->table("KDunitprice")->count(); echo "KDunitprice 총 건수: {$count}"; ' ``` ### 4.3 검사비 신규 등록 (Phase 1.1) ```bash # 검사비 아이템 신규 등록 docker exec sam-api-1 php artisan tinker --execute=' $item = \App\Models\Items\Item::create([ "tenant_id" => 287, "code" => "EST-INSPECTION", "name" => "검사비", "unit" => "EA", "item_type" => "product", "is_active" => true, ]); echo "Created: ID={$item->id}, Code={$item->code}"; ' ``` --- ## 5. 코드 수정 가이드 ### 5.1 수정 대상 파일 ``` api/app/Services/Quote/FormulaHandlers/KyungdongFormulaHandler.php ``` ### 5.2 수정 패턴 (예시: calculateSteelItems 내 케이스 마구리) **현재 코드 (item_code 없음):** ```php $items[] = [ 'item_name' => '케이스 마구리', 'item_code' => null, // ❌ 없음 'item_id' => null, // ❌ 없음 'quantity' => $quantity, 'unit_price' => $unitPrice, 'total_price' => $quantity * $unitPrice, // ... ]; ``` **수정 후 (item_code/item_id 매핑):** ```php // 규격 계산 (예: 505*355) $spec = "{$caseWidth}*{$caseDepth}"; $itemCode = "BD-마구리-{$spec}"; // items master에서 lookup $item = \App\Models\Items\Item::where('tenant_id', $this->tenantId) ->where('code', $itemCode) ->first(); $items[] = [ 'item_name' => '케이스 마구리', 'item_code' => $item?->code ?? $itemCode, // ✅ 코드 매핑 'item_id' => $item?->id, // ✅ ID 매핑 'quantity' => $quantity, 'unit_price' => $unitPrice, 'total_price' => $quantity * $unitPrice, // ... ]; ``` ### 5.3 아이템 lookup 헬퍼 메서드 추가 (권장) ```php /** * items master에서 코드로 아이템 조회 */ private function lookupItem(string $code): ?Item { return Item::where('tenant_id', $this->tenantId) ->where('code', $code) ->first(); } /** * 아이템 배열에 item_code/item_id 추가 */ private function withItemMapping(array $item, string $code): array { $masterItem = $this->lookupItem($code); return array_merge($item, [ 'item_code' => $masterItem?->code ?? $code, 'item_id' => $masterItem?->id, ]); } ``` --- ## 6. 검증 방법 ### 6.1 견적 산출 후 BOM 결과 확인 ```bash # 최근 견적의 BOM 결과에서 item_code 확인 docker exec sam-api-1 php artisan tinker --execute=' $quote = \App\Models\Quote::where("tenant_id", 287) ->whereNotNull("calculation_result") ->latest() ->first(); $items = $quote->calculation_result["items"] ?? []; $noCode = array_filter($items, fn($i) => empty($i["item_code"])); echo "총 아이템: " . count($items) . "개" . PHP_EOL; echo "item_code 없음: " . count($noCode) . "개" . PHP_EOL; foreach ($noCode as $i) { echo " - {$i["item_name"]}" . PHP_EOL; } ' ``` ### 6.2 수주 등록 화면 확인 1. `/orders/create?quoteId={ID}`로 수주 등록 화면 진입 2. 아이템 목록에서 동일 아이템이 코드 기준으로 집계되는지 확인 3. 3개소에 "환봉" 각 1개 → "환봉" 1행, 수량 3개로 표시되어야 함 --- ## 7. 성공 기준 | 기준 | 검증 방법 | 달성 | |------|----------|:----:| | BOM 산출 22종 아이템 전부 item_code 보유 | Phase 3.1 검증 쿼리 | ⏳ | | BOM 산출 22종 아이템 전부 item_id 보유 | Phase 3.1 검증 쿼리 | ⏳ | | 수주 등록 시 코드 기반 아이템 집계 정상 동작 | Phase 3.2 화면 확인 | ⏳ | | 기존 견적 산출 금액에 영향 없음 | 기존 견적 재산출 후 금액 비교 | ⏳ | --- ## 8. 관련 소스 파일 | 파일 | 수정 여부 | 용도 | |------|:--------:|------| | `api/app/Services/Quote/FormulaHandlers/KyungdongFormulaHandler.php` | **수정** | BOM 산출 매핑 로직 추가 | | `api/app/Models/Items/Item.php` | 읽기 | items lookup | | `react/src/components/orders/OrderRegistration.tsx` | 검증 | 수주 등록 아이템 그룹핑 | | `react/src/components/orders/actions.ts` | 검증 | 수주 데이터 변환 | --- ## 9. 참고 정보 ### 9.1 SAM 견적 전용 코드 체계 | 접두사 | 용도 | 예시 | |--------|------|------| | BD- | 절곡품 (모델/규격별) | BD-케이스-500*350, BD-마구리-505*355 | | EST- | 견적 산출 전용 | EST-MOTOR-220V-300K, EST-INSPECTION | | PT- | 품목 템플릿 (규격 미포함) | PT-케이스, PT-가이드레일 | | PM- | 제어기 부품 | PM-020 (제어기 노출형) | ### 9.2 5130 DB 연결 정보 ``` # api/.env CHANDJ_DB_HOST=sam-mysql-1 CHANDJ_DB_DATABASE=chandj CHANDJ_DB_USERNAME=root CHANDJ_DB_PASSWORD=root ``` ### 9.3 상세 분석 문서 - 전체 분석 결과: `docs/data/analysis/bom-item-mapping-analysis.md` - 견적 시스템 구조: `docs/features/quotes/README.md` --- ## 10. 컨펌 대기 목록 | # | 항목 | 변경 내용 | 승인 | |---|------|----------|:----:| | 1 | 검사비 신규 등록 | items master에 EST-INSPECTION 추가 | ⏳ | | 2 | 무게평철12T 동일성 | BOM의 무게평철12T = 00021 평철12T 인지 | ⏳ | --- ## 11. 변경 이력 | 날짜 | 작업 | 변경 내용 | 파일 | |------|------|----------|------| | 2026-02-05 | 분석 | 22종 아이템 매핑 상태 분석 완료 | bom-item-mapping-analysis.md | | 2026-02-05 | 계획 | 작업 계획 문서 작성 | bom-item-mapping-plan.md | | 2026-02-05 | Phase 1 | 검사비(EST-INSPECTION) ID:14913 신규 등록 | items master | | 2026-02-05 | Phase 2 | KyungdongFormulaHandler 매핑 로직 추가 | KyungdongFormulaHandler.php | --- *이 문서는 /sc:plan 스킬로 생성되었습니다.*