# MNG 품목관리 - 견적수식 엔진(FormulaEvaluatorService) 연동 계획 > **작성일**: 2026-02-19 > **목적**: 가변사이즈 완제품(FG) 선택 시 오픈사이즈(W,H) 입력 → FormulaEvaluatorService로 동적 자재 산출 → 중앙 패널에 트리 표시 > **기준 문서**: docs/dev_plans/mng-item-management-plan.md, api/app/Services/Quote/FormulaEvaluatorService.php > **선행 작업**: 3-Panel 품목관리 페이지 구현 완료 (Phase 1~2 of mng-item-management-plan.md) > **상태**: 🔄 진행중 --- ## 📍 현재 진행 상태 | 항목 | 내용 | |------|------| | **마지막 완료 작업** | Phase 2.5: 로딩/에러 상태 처리 (전체 구현 완료) | | **다음 작업** | 검증 (브라우저 테스트) | | **진행률** | 8/8 (100%) | | **마지막 업데이트** | 2026-02-19 | --- ## 1. 개요 ### 1.1 배경 MNG 품목관리 페이지(`mng.sam.kr/item-management`)에서 완제품(FG) `FG-KQTS01-벽면형-SUS`를 선택하면 `items.bom` JSON에 등록된 정적 BOM(PT 2개: 가이드레일, 하단마감재)만 표시된다. 그러나 견적관리(`dev.sam.kr/sales/quote-management/46`)에서는 `FormulaEvaluatorService`가 W=3000, H=3000 입력으로 17종 51개의 자재를 동적 산출한다. **핵심 문제**: items.bom(정적)과 FormulaEvaluatorService(동적) 두 시스템이 분리되어 있어, 품목관리 페이지에서 실제 필요 자재를 볼 수 없다. **해결**: 가변사이즈 품목(`item_details.is_variable_size = true`) 선택 시 오픈사이즈(W, H) 입력 UI를 제공하고, API의 기존 엔드포인트(`POST /api/v1/quotes/calculate/bom`)를 MNG에서 HTTP로 호출하여 산출 결과를 중앙 패널에 표시한다. ### 1.2 기준 원칙 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 🎯 핵심 원칙 │ ├─────────────────────────────────────────────────────────────────┤ │ - MNG에서 마이그레이션 파일 생성 금지 (API에서만) │ │ - MNG ↔ API 통신은 HTTP API 호출 (코드 공유/직접 서비스 호출 X) │ │ - 기존 API 엔드포인트 재사용 (POST /api/v1/quotes/calculate/bom)│ │ - Docker nginx 내부 라우팅 + SSL 우회 패턴 사용 │ │ - Blade + HTMX + Tailwind + Vanilla JS (Alpine.js 미사용) │ │ - 정적 BOM과 수식 산출 결과를 탭으로 전환 가능하게 │ │ - Controller에서 직접 DB 쿼리 금지 (Service-First) │ │ - Controller에서 직접 validate() 금지 (FormRequest 필수) │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.3 변경 승인 정책 | 분류 | 예시 | 승인 | |------|------|------| | ✅ 즉시 가능 | 서비스 생성, 컨트롤러 메서드 추가, Blade 수정, JS 추가 | 불필요 | | ⚠️ 컨펌 필요 | API 라우트 추가, 기존 Blade 구조 변경 | **필수** | | 🔴 금지 | mng에서 마이그레이션 생성, API 소스 수정 | 별도 협의 | ### 1.4 MNG 절대 금지 규칙 ``` ❌ mng/database/migrations/ 에 파일 생성 금지 ❌ docker exec sam-mng-1 php artisan migrate 실행 금지 ❌ php artisan db:seed --class=*MenuSeeder 실행 금지 ❌ Controller에서 직접 DB 쿼리 금지 (Service-First) ❌ Controller에서 직접 validate() 금지 (FormRequest 필수) ❌ api/ 프로젝트 소스 코드 수정 금지 ``` --- ## 2. 대상 범위 ### 2.1 Phase 1: MNG 백엔드 (HTTP API 호출 서비스) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1.1 | FormulaApiService 생성 (MNG→API HTTP 호출 래퍼) | ✅ | 신규 파일 | | 1.2 | ItemManagementApiController에 calculateFormula 메서드 추가 | ✅ | 기존 파일 수정 | | 1.3 | API 라우트 추가 (POST /api/admin/items/{id}/calculate-formula) | ✅ | 기존 파일 수정 | ### 2.2 Phase 2: MNG 프론트엔드 (UI 연동) | # | 작업 항목 | 상태 | 비고 | |---|----------|:----:|------| | 2.1 | 중앙 패널 헤더에 탭 UI 추가 (정적 BOM / 수식 산출) | ✅ | index.blade.php 수정 | | 2.2 | 오픈사이즈 입력 폼 (W, H, 수량) + 산출 버튼 | ✅ | index.blade.php 수정 | | 2.3 | 수식 산출 결과 트리 렌더링 (카테고리 그룹별) | ✅ | JS 추가 | | 2.4 | 가변사이즈 품목 감지 → 자동 탭 전환 | ✅ | item-detail.blade.php + JS 수정 | | 2.5 | 로딩/에러 상태 처리 | ✅ | JS 추가 | --- ## 3. 이미 구현된 코드 (선행 작업 - 수정 대상) > 새 세션에서 현재 코드 상태를 파악할 수 있도록 이미 존재하는 파일 전체 목록과 핵심 구조를 기록. ### 3.1 파일 구조 (이미 존재) ``` mng/ ├── app/ │ ├── Http/Controllers/ │ │ ├── ItemManagementController.php # Web (HX-Redirect 패턴) │ │ └── Api/Admin/ │ │ └── ItemManagementApiController.php # API (index, bomTree, detail) │ ├── Models/ │ │ ├── Items/ │ │ │ ├── Item.php # BelongsToTenant, 관계, 스코프, 상수 │ │ │ └── ItemDetail.php # 1:1 확장 (is_variable_size 필드 포함) │ │ └── Commons/ │ │ └── File.php # 파일 모델 │ ├── Services/ │ │ └── ItemManagementService.php # getItemList, getBomTree, getItemDetail │ └── Traits/ │ └── BelongsToTenant.php # 테넌트 격리 Trait ├── resources/views/item-management/ │ ├── index.blade.php # 3-Panel 메인 (★ 수정 대상) │ └── partials/ │ ├── item-list.blade.php # 좌측 패널 (변경 없음) │ ├── bom-tree.blade.php # 중앙 패널 초기 상태 (변경 없음) │ └── item-detail.blade.php # 우측 패널 (★ 수정 대상) ├── routes/ │ ├── web.php # Route: GET /item-management (변경 없음) │ └── api.php # Route: items group (★ 수정 대상 - 라우트 추가) └── config/ └── api-explorer.php # FLOW_TESTER_API_KEY 설정 참조 ``` ### 3.2 현재 ItemManagementApiController 전체 (수정 대상) ```php service->getItemList([ 'search' => $request->input('search'), 'item_type' => $request->input('item_type'), 'per_page' => $request->input('per_page', 50), ]); return view('item-management.partials.item-list', compact('items')); } public function bomTree(int $id, Request $request): JsonResponse { $maxDepth = $request->input('max_depth', 10); $tree = $this->service->getBomTree($id, $maxDepth); return response()->json($tree); } public function detail(int $id): View { $data = $this->service->getItemDetail($id); return view('item-management.partials.item-detail', [ 'item' => $data['item'], 'bomChildren' => $data['bom_children'], ]); } } ``` ### 3.3 현재 API 라우트 (items 그룹, mng/routes/api.php:866~) ```php Route::middleware(['web', 'auth', 'hq.member'])->prefix('admin/items')->name('api.admin.items.')->group(function () { Route::get('/search', [ItemApiController::class, 'search'])->name('search'); // 품목관리 페이지 API Route::get('/', [ItemManagementApiController::class, 'index'])->name('index'); Route::get('/{id}/bom-tree', [ItemManagementApiController::class, 'bomTree'])->name('bom-tree'); Route::get('/{id}/detail', [ItemManagementApiController::class, 'detail'])->name('detail'); // ★ 여기에 calculate-formula 라우트 추가 예정 }); ``` ### 3.4 현재 index.blade.php 중앙 패널 (수정 대상 부분) ```html
좌측에서 품목을 선택하세요.
좌측에서 품목을 선택하세요.
${data.error || '산출 실패'}
서버 연결 실패
산출된 자재가 없습니다.
'; } } ``` --- ## 5. 컨펌 대기 목록 | # | 항목 | 변경 내용 | 영향 범위 | 상태 | |---|------|----------|----------|------| | 1 | ~~API 인증 방식~~ | X-API-KEY 필수 (FLOW_TESTER_API_KEY) | MNG→API 통신 | ✅ 확인 완료 | | 2 | API 라우트 추가 | POST /api/admin/items/{id}/calculate-formula | mng/routes/api.php | ✅ 즉시 가능 | --- ## 6. 변경 이력 | 날짜 | 항목 | 변경 내용 | 파일 | 승인 | |------|------|----------|------|------| | 2026-02-19 | - | 계획 문서 초안 작성 | - | - | | 2026-02-19 | - | 자기완결성 보강 (기존 코드 현황, API 인증 분석, 트러블슈팅) | - | - | | 2026-02-19 | 1.1~1.3 | Phase 1 백엔드 구현 완료 (FormulaApiService, Controller, Route) | FormulaApiService.php, ItemManagementApiController.php, api.php | ✅ | | 2026-02-19 | 2.1~2.5 | Phase 2 프론트엔드 구현 완료 (탭 UI, 입력 폼, 트리 렌더링, 감지, 에러) | index.blade.php, item-detail.blade.php | ✅ | --- ## 7. 참고 문서 - **기존 품목관리 계획**: `docs/dev_plans/mng-item-management-plan.md` - **FormulaEvaluatorService**: `api/app/Services/Quote/FormulaEvaluatorService.php` - 메서드: `calculateBomWithDebug(string $finishedGoodsCode, array $inputVariables, ?int $tenantId): array` - tenant_id=287 자동 감지 → KyungdongFormulaHandler 라우팅 - **KyungdongFormulaHandler**: `api/app/Services/Quote/Handlers/KyungdongFormulaHandler.php` - `calculateDynamicItems(array $inputs)` → steel(10종), part(3종), motor/controller 산출 - **API 라우트**: `api/routes/api/v1/sales.php:64` → `QuoteController::calculateBom` - **QuoteBomCalculateRequest**: `api/app/Http/Requests/V1/QuoteBomCalculateRequest.php` - `finished_goods_code` (required|string) - `variables` (required|array), `variables.W0` (required|numeric), `variables.H0` (required|numeric) - `tenant_id` (nullable|integer) - **MNG-API HTTP 패턴**: `mng/app/Services/FlowTester/HttpClient.php` - **API Key 설정**: `mng/config/api-explorer.php:26` → `env('FLOW_TESTER_API_KEY')` - **현재 품목관리 뷰**: `mng/resources/views/item-management/index.blade.php` - **MNG 프로젝트 규칙**: `mng/CLAUDE.md` --- ## 8. 세션 및 메모리 관리 정책 ### 8.1 세션 시작 시 (Load Strategy) ``` 1. 이 문서 읽기 (docs/dev_plans/mng-item-formula-integration-plan.md) 2. 📍 현재 진행 상태 확인 → 다음 작업 파악 3. 섹션 3 "이미 구현된 코드" 확인 → 수정 대상 파일 파악 4. 필요시 Serena 메모리 로드: read_memory("item-formula-state") read_memory("item-formula-snapshot") read_memory("item-formula-active-symbols") ``` ### 8.2 작업 중 관리 (Context Defense) | 컨텍스트 잔량 | Action | 내용 | |--------------|--------|------| | **30% 이하** | Snapshot | `write_memory("item-formula-snapshot", "코드변경+논의요약")` | | **20% 이하** | Context Purge | `write_memory("item-formula-active-symbols", "주요 수정 파일/함수")` | | **10% 이하** | Stop & Save | 최종 상태 저장 후 세션 교체 권고 | --- ## 9. 검증 결과 ### 9.1 테스트 케이스 | # | 테스트 | 입력 | 예상 결과 | 실제 결과 | 상태 | |---|--------|------|----------|----------|------| | 1 | FG 가변사이즈 품목 선택 | FG-KQTS01 클릭 | 수식 산출 탭 자동 표시, 입력 폼 노출 | | ⏳ | | 2 | 오픈사이즈 입력 후 산출 | W:3000, H:3000, QTY:1 | 17종 자재 트리 (steel/part/motor 그룹별), 소계/합계 표시 | | ⏳ | | 3 | 비가변사이즈 품목 선택 | PT 품목 클릭 | 수식 산출 탭 숨김, 정적 BOM만 표시 | | ⏳ | | 4 | 정적 BOM ↔ 수식 산출 탭 전환 | 탭 클릭 | 각 탭 콘텐츠 전환, 입력 폼 표시/숨김 | | ⏳ | | 5 | 산출 결과에서 품목 클릭 | 트리 노드 클릭 | 좌측 검색에 품목코드 입력 → 품목 리스트 필터링 | | ⏳ | | 6 | API Key 미설정 | FLOW_TESTER_API_KEY 없음 | 에러 메시지 "API 응답 오류: HTTP 401" + 재시도 버튼 | | ⏳ | | 7 | 입력값 범위 초과 | W:0, H:-1 | alert 표시, API 호출 안 함 | | ⏳ | | 8 | 서버 연결 실패 | nginx 중지 상태 | 에러 메시지 "서버 연결 실패" + 재시도 버튼 | | ⏳ | ### 9.2 성공 기준 달성 현황 | 기준 | 달성 | 비고 | |------|------|------| | FG 가변사이즈 품목에서 수식 산출 가능 | ⏳ | | | 산출 결과가 견적관리와 동일한 17종 자재 표시 | ⏳ | | | 정적 BOM과 수식 산출 탭 전환 작동 | ⏳ | | | 비가변사이즈 품목은 기존 정적 BOM만 표시 | ⏳ | | | 에러 처리 및 로딩 상태 표시 | ⏳ | | --- ## 10. 자기완결성 점검 결과 ### 10.1 체크리스트 검증 | # | 검증 항목 | 상태 | 비고 | |---|----------|:----:|------| | 1 | 작업 목적이 명확한가? | ✅ | 가변사이즈 FG 품목의 동적 자재 산출 표시 | | 2 | 성공 기준이 정의되어 있는가? | ✅ | 9.2 성공 기준 5개 항목 | | 3 | 작업 범위가 구체적인가? | ✅ | Phase 1 백엔드 3건, Phase 2 프론트 5건 | | 4 | 의존성이 명시되어 있는가? | ✅ | API 엔드포인트 인증 분석 완료, Docker 라우팅 패턴 | | 5 | 참고 파일 경로가 정확한가? | ✅ | 모든 파일 경로 검증 완료 | | 6 | 단계별 절차가 실행 가능한가? | ✅ | 전체 구현 코드 포함 | | 7 | 검증 방법이 명시되어 있는가? | ✅ | 9.1 테스트 케이스 8개 | | 8 | 모호한 표현이 없는가? | ✅ | 구체적 수치/코드로 기술 | | 9 | 기존 코드 현황이 명시되어 있는가? | ✅ | 섹션 3에 전체 파일 구조 + 핵심 코드 인라인 | | 10 | API 인증 방식이 확정되었는가? | ✅ | X-API-KEY 필수 (FLOW_TESTER_API_KEY) | | 11 | 트러블슈팅 가이드가 있는가? | ✅ | 4.1 FormulaApiService 트러블슈팅 섹션 | ### 10.2 새 세션 시뮬레이션 테스트 | 질문 | 답변 가능 | 참조 섹션 | |------|:--------:|----------| | Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 | | Q2. 어디서부터 시작해야 하는가? | ✅ | 📍 현재 진행 상태 + 4.1 Phase 1 | | Q3. 어떤 파일을 수정/생성해야 하는가? | ✅ | 3.1 파일 구조 + 2. 대상 범위 | | Q4. 현재 코드 상태는 어떤가? | ✅ | 3.2~3.6 기존 코드 현황 | | Q5. API 인증은 어떻게 하는가? | ✅ | 4.1 FormulaApiService (인증 테이블) | | Q6. 테넌트 필터링은 어떻게 동작하는가? | ✅ | 3.6 테넌트 필터링 패턴 | | Q7. 작업 완료 확인 방법은? | ✅ | 9. 검증 결과 | | Q8. 막혔을 때 참고 문서는? | ✅ | 7. 참고 문서 + 4.1 트러블슈팅 | **결과**: 8/8 통과 → ✅ 자기완결성 확보 --- *이 문서는 /sc:plan 스킬로 생성되었습니다. 자기완결성 보강: 2026-02-19*