diff --git a/plans/mng-quote-formula-development-plan.md b/plans/mng-quote-formula-development-plan.md new file mode 100644 index 0000000..6288780 --- /dev/null +++ b/plans/mng-quote-formula-development-plan.md @@ -0,0 +1,952 @@ +# MNG 견적수식 관리 개발 계획 + +> **작성일**: 2025-12-22 +> **상태**: 계획 수립 +> **대상**: mng.sam.kr/quote-formulas + +--- + +## 1. 현황 분석 + +### 1.1 MNG 프로젝트 현재 상태 + +#### 구현된 기능 (mng) + +| 기능 | 상태 | 설명 | +|-----|------|-----| +| 수식 목록 | ✅ 완료 | 페이지네이션, 필터링, HTMX 테이블 | +| 수식 생성 | ✅ 완료 | 카테고리, 유형, 변수명, 수식 입력 | +| 수식 수정 | ✅ 완료 | 편집 폼, API 연동 | +| 수식 삭제 | ✅ 완료 | Soft Delete, 복원, 영구삭제 | +| 수식 복제 | ✅ 완료 | 수식 복사 기능 | +| 활성/비활성 | ✅ 완료 | 토글 기능 | +| 카테고리 관리 | ✅ 완료 | CRUD 구현 | +| 시뮬레이터 | ✅ 완료 | 입력값 → 계산 결과 미리보기 | +| 변수 참조 | ✅ 완료 | 사용 가능한 변수 목록 표시 | +| 수식 검증 | ✅ 완료 | 문법 검증 API | + +#### 미구현/미완성 기능 (mng) + +| 기능 | 상태 | 설명 | +|-----|------|-----| +| 범위(Range) 관리 UI | ❌ 미구현 | 범위별 결과 설정 화면 없음 | +| 매핑(Mapping) 관리 UI | ❌ 미구현 | 매핑 규칙 설정 화면 없음 | +| 품목(Item) 관리 UI | ❌ 미구현 | 출력 품목 설정 화면 없음 | +| 5130 데이터 연동 | ❌ 미구현 | 레거시 가격 데이터 동기화 | + +### 1.2 API 프로젝트 현재 상태 + +#### 모델 구조 (api) + +``` +QuoteFormulaCategory (카테고리) +└── QuoteFormula (수식) + ├── QuoteFormulaRange (범위 조건) + ├── QuoteFormulaMapping (매핑 규칙) + └── QuoteFormulaItem (출력 품목) +``` + +#### 시더 데이터 (api) + +| 시더 | 데이터 수 | 설명 | +|-----|---------|-----| +| QuoteFormulaCategorySeeder | 11개 | 카테고리 (오픈사이즈~단가수식) | +| QuoteFormulaSeeder | 30개 수식, 18개 범위 | 스크린 계산 수식 | +| QuoteFormulaItemSeeder | 25개 | 품목 마스터 (5130 가격 적용) | + +#### 서비스 (api) + +| 서비스 | 역할 | +|-------|-----| +| QuoteCalculationService | 자동산출 실행 엔진 | +| FormulaEvaluatorService | 수식 평가, 범위/매핑 처리 | +| QuoteService | 견적 CRUD, 상태 관리 | +| QuoteNumberService | 견적번호 생성 | +| QuoteDocumentService | PDF/이메일/카카오 발송 (TODO) | + +--- + +## 2. MNG vs API 비교 분석 + +### 2.1 데이터 구조 비교 + +| 항목 | MNG | API | 일치 | +|-----|-----|-----|-----| +| quote_formula_categories | ✅ | ✅ | ✅ | +| quote_formulas | ✅ | ✅ | ✅ | +| quote_formula_ranges | ✅ | ✅ | ✅ | +| quote_formula_mappings | ✅ | ✅ | ✅ | +| quote_formula_items | ✅ | ✅ | ✅ | + +**결론**: 모델 구조는 동일함 (같은 DB 사용) + +### 2.2 기능 비교 + +| 기능 | MNG | API | 비고 | +|-----|-----|-----|-----| +| 수식 CRUD | ✅ | ✅ | 동일 | +| 카테고리 CRUD | ✅ | ✅ | 동일 | +| **범위 관리 UI** | ❌ | ✅ (시더) | MNG에 UI 필요 | +| **매핑 관리 UI** | ❌ | ✅ (시더) | MNG에 UI 필요 | +| **품목 관리 UI** | ❌ | ✅ (시더) | MNG에 UI 필요 | +| 시뮬레이터 | ✅ | ✅ | 동일 | +| 자동산출 API | - | ✅ | API 전용 | + +### 2.3 핵심 차이점 + +``` +MNG (관리 UI) +├── 수식 기본 정보 관리 ✅ +├── 카테고리 관리 ✅ +├── 시뮬레이터 ✅ +└── 범위/매핑/품목 관리 ❌ ← 개발 필요 + +API (자동산출 엔진) +├── 시더로 데이터 주입 ✅ +├── 자동산출 서비스 ✅ +└── 견적 생성 API ✅ +``` + +--- + +## 3. 개발 계획 + +### 3.1 목표 + +MNG에서 **범위(Range), 매핑(Mapping), 품목(Item)** 관리 UI를 추가하여: +1. 시더 없이도 관리자가 직접 수식 규칙 설정 가능 +2. 5130 레거시 데이터를 참조하여 가격 설정 가능 +3. 실시간 시뮬레이션으로 설정 검증 가능 + +### 3.2 개발 범위 + +#### Phase 1: 범위(Range) 관리 UI + +**우선순위**: 높음 +**이유**: 모터, 가이드레일, 케이스 자동 선택에 필수 + +**기능 목록**: +1. 수식 상세 페이지에 범위 관리 탭 추가 +2. 범위 목록 표시 (min ~ max → 결과) +3. 범위 추가/수정/삭제 +4. 드래그앤드롭 순서 변경 +5. item_code 연결 (품목 선택) + +**화면 설계**: +``` +[수식 수정] 페이지 +├── [기본 정보] 탭 (기존) +├── [범위 설정] 탭 ← 추가 +│ ├── 조건 변수: [K (중량)] ▼ +│ ├── 범위 목록 +│ │ ┌─────────────────────────────────────────────────┐ +│ │ │ # │ 최소값 │ 최대값 │ 결과값 │ 품목코드 │ +│ │ ├─────────────────────────────────────────────────┤ +│ │ │ 1 │ 0 │ 150 │ 150K │ PT-MOTOR-150│ +│ │ │ 2 │ 150 │ 300 │ 300K │ PT-MOTOR-300│ +│ │ │ 3 │ 300 │ 400 │ 400K │ PT-MOTOR-400│ +│ │ └─────────────────────────────────────────────────┘ +│ └── [+ 범위 추가] +├── [매핑 설정] 탭 +└── [품목 설정] 탭 +``` + +**API 엔드포인트 (MNG 내부)**: +``` +GET /api/admin/quote-formulas/formulas/{id}/ranges +POST /api/admin/quote-formulas/formulas/{id}/ranges +PUT /api/admin/quote-formulas/formulas/{id}/ranges/{rangeId} +DELETE /api/admin/quote-formulas/formulas/{id}/ranges/{rangeId} +POST /api/admin/quote-formulas/formulas/{id}/ranges/reorder +``` + +#### Phase 2: 매핑(Mapping) 관리 UI + +**우선순위**: 중간 +**이유**: 제어기 유형 등 코드 매핑에 사용 + +**기능 목록**: +1. 수식 상세 페이지에 매핑 관리 탭 추가 +2. 매핑 목록 표시 (소스값 → 결과값) +3. 매핑 추가/수정/삭제 + +**화면 설계**: +``` +[매핑 설정] 탭 +├── 소스 변수: [CONTROL_TYPE] ▼ +├── 매핑 목록 +│ ┌──────────────────────────────────────────────────┐ +│ │ # │ 소스값 │ 결과값 │ 품목코드 │ +│ ├──────────────────────────────────────────────────┤ +│ │ 1 │ EMB │ 매립형 │ PT-CTRL-EMB │ +│ │ 2 │ EXP │ 노출형 │ PT-CTRL-EXP │ +│ │ 3 │ BOX_1P │ 콘트롤박스 │ PT-CTRL-BOX-1P │ +│ └──────────────────────────────────────────────────┘ +└── [+ 매핑 추가] +``` + +#### Phase 3: 품목(Item) 관리 UI + +**우선순위**: 중간 +**이유**: 수식 결과로 생성되는 품목 정의 + +**기능 목록**: +1. 수식 상세 페이지에 품목 관리 탭 추가 +2. 품목 목록 표시 +3. 품목 추가/수정/삭제 +4. 수량/단가 수식 입력 +5. 5130 품목 검색 및 가격 참조 + +**화면 설계**: +``` +[품목 설정] 탭 +├── 품목 목록 +│ ┌───────────────────────────────────────────────────────────┐ +│ │ 품목코드 │ 품목명 │ 규격 │ 수량식 │ 단가식│ +│ ├───────────────────────────────────────────────────────────┤ +│ │ PT-MOTOR-150 │ 개폐전동기 150kg│ 150K(S) │ 1 │ 285000│ +│ │ PT-GR-3000 │ 가이드레일 3000 │ 3000mm │ 2 │ 42000 │ +│ └───────────────────────────────────────────────────────────┘ +├── [+ 품목 추가] +└── [5130에서 가져오기] ← 레거시 연동 +``` + +#### Phase 4: 5130 데이터 연동 + +**우선순위**: 높음 +**이유**: 실제 가격 데이터 필요 + +**기능 목록**: +1. 5130 DB 가격 테이블 조회 API +2. 품목 추가 시 5130 검색 모달 +3. 가격 동기화 기능 +4. 가격 변경 이력 관리 + +**5130 테이블 참조**: +```sql +-- chandj.price_motor: 모터 가격 +-- chandj.price_*: 기타 품목 가격 +``` + +### 3.3 파일 수정/추가 목록 + +#### Routes (mng/routes/web.php) +```php +// 기존 라우트에 추가 +Route::prefix('quote-formulas')->group(function () { + // ... 기존 라우트 + Route::get('/{id}/ranges', [QuoteFormulaController::class, 'ranges']); + Route::get('/{id}/mappings', [QuoteFormulaController::class, 'mappings']); + Route::get('/{id}/items', [QuoteFormulaController::class, 'items']); +}); +``` + +#### API Routes (mng/routes/api.php) +```php +// 범위/매핑/품목 CRUD API +Route::prefix('quote-formulas/formulas/{formulaId}')->group(function () { + Route::apiResource('ranges', QuoteFormulaRangeController::class); + Route::post('ranges/reorder', [QuoteFormulaRangeController::class, 'reorder']); + + Route::apiResource('mappings', QuoteFormulaMappingController::class); + + Route::apiResource('items', QuoteFormulaItemController::class); +}); + +// 5130 연동 +Route::prefix('legacy')->group(function () { + Route::get('items/search', [LegacyController::class, 'searchItems']); + Route::get('prices/{itemCode}', [LegacyController::class, 'getPrice']); +}); +``` + +#### Controllers +``` +app/Http/Controllers/ +├── QuoteFormulaController.php (수정: 탭 추가) +└── Api/Admin/Quote/ + ├── QuoteFormulaRangeController.php (신규) + ├── QuoteFormulaMappingController.php (신규) + ├── QuoteFormulaItemController.php (신규) + └── LegacyController.php (신규: 5130 연동) +``` + +#### Services +``` +app/Services/ +├── Quote/ +│ ├── QuoteFormulaRangeService.php (신규) +│ ├── QuoteFormulaMappingService.php (신규) +│ └── QuoteFormulaItemService.php (신규) +└── Legacy/ + └── LegacyPriceService.php (신규: 5130 가격 조회) +``` + +#### Views +``` +resources/views/quote-formulas/ +├── edit.blade.php (수정: 탭 구조로 변경) +├── partials/ +│ ├── ranges-tab.blade.php (신규) +│ ├── mappings-tab.blade.php (신규) +│ └── items-tab.blade.php (신규) +└── modals/ + ├── range-form.blade.php (신규) + ├── mapping-form.blade.php (신규) + ├── item-form.blade.php (신규) + └── legacy-item-search.blade.php (신규) +``` + +### 3.4 개발 순서 + +``` +Phase 1: 범위 관리 (1주) +├── Day 1-2: API 엔드포인트 구현 +├── Day 3-4: UI 컴포넌트 구현 +└── Day 5: 테스트 및 검증 + +Phase 2: 매핑 관리 (0.5주) +├── Day 1: API 구현 +└── Day 2-3: UI 구현 + +Phase 3: 품목 관리 (0.5주) +├── Day 1: API 구현 +└── Day 2-3: UI 구현 + +Phase 4: 5130 연동 (1주) +├── Day 1-2: 레거시 DB 조회 서비스 +├── Day 3-4: 검색 모달 UI +└── Day 5: 가격 동기화 기능 + +통합 테스트 (0.5주) +├── 시뮬레이터 연동 테스트 +└── 전체 플로우 검증 +``` + +--- + +## 4. 기술 스택 + +### 4.1 Frontend (MNG) +- **Framework**: Laravel Blade + Alpine.js +- **Styling**: Tailwind CSS + DaisyUI +- **AJAX**: HTMX (hx-get, hx-post, hx-delete) +- **Modal**: DaisyUI modal 컴포넌트 + +### 4.2 Backend (MNG) +- **Framework**: Laravel 12 +- **ORM**: Eloquent +- **DB**: MySQL (samdb + chandj) +- **Auth**: Session 기반 + +### 4.3 API 연동 +- MNG 내부 API (`/api/admin/quote-formulas/*`) +- 5130 DB 직접 조회 (chandj 데이터베이스) + +--- + +## 5. 데이터 마이그레이션 + +### 5.1 현재 상태 +- API 시더로 30개 수식, 18개 범위, 25개 품목 등록됨 +- 5130 실제 가격 데이터 반영 완료 + +### 5.2 마이그레이션 계획 +1. **Phase 1 완료 후**: 시더 데이터를 MNG UI로 확인 가능 +2. **Phase 4 완료 후**: 5130 데이터 자동 동기화 + +--- + +## 6. 검증 계획 + +### 6.1 시뮬레이터 테스트 +``` +입력: W0=3000, H0=2500 +예상 결과: + - CASE: PT-CASE-3600 (S=3270) + - GR: PT-GR-3000 (H1=2770) + - MOTOR: PT-MOTOR-150 (K=41.21kg) +``` + +### 6.2 CRUD 테스트 +- 범위 추가/수정/삭제 후 시뮬레이터 결과 확인 +- 품목 가격 변경 후 합계 확인 + +--- + +## 7. 참고 자료 + +### 7.1 기존 파일 위치 (MNG) +``` +mng/ +├── app/Http/Controllers/ +│ ├── QuoteFormulaController.php +│ └── Api/Admin/Quote/QuoteFormulaController.php +├── app/Services/Quote/ +│ └── QuoteFormulaService.php +├── app/Models/Quote/ +│ ├── QuoteFormula.php +│ ├── QuoteFormulaCategory.php +│ ├── QuoteFormulaRange.php +│ ├── QuoteFormulaMapping.php +│ └── QuoteFormulaItem.php +└── resources/views/quote-formulas/ + ├── index.blade.php + ├── create.blade.php + ├── edit.blade.php + └── simulator.blade.php +``` + +### 7.2 API 시더 위치 +``` +api/database/seeders/ +├── QuoteFormulaCategorySeeder.php +├── QuoteFormulaSeeder.php +└── QuoteFormulaItemSeeder.php +``` + +### 7.3 5130 가격 테이블 +```sql +chandj.price_motor -- 모터 가격 +chandj.price_* -- 기타 품목 가격 (확인 필요) +``` + +--- + +## 8. 리스크 및 대응 + +| 리스크 | 영향 | 대응 | +|-------|-----|-----| +| 5130 DB 스키마 변경 | 중 | JSON 파싱 로직 유연하게 구현 | +| MNG-API 데이터 불일치 | 높 | 동일 DB 사용으로 해결됨 | +| 시뮬레이터 성능 저하 | 낮 | 수식 캐싱 적용 | + +--- + +## 9. 다음 단계 + +1. **승인 요청**: 이 계획에 대한 검토 및 승인 +2. **Phase 1 착수**: 범위 관리 UI 개발 시작 +3. **주간 리뷰**: 진행 상황 점검 + +--- + +## 10. 코딩 컨벤션 및 예시 코드 + +### 10.1 API Controller 패턴 (MNG) + +```php +rangeService->getRangesByFormula($formulaId); + + return response()->json([ + 'success' => true, + 'data' => $ranges, + ]); + } + + /** + * 범위 생성 + */ + public function store(Request $request, int $formulaId): JsonResponse + { + $validated = $request->validate([ + 'min_value' => 'nullable|numeric', + 'max_value' => 'nullable|numeric', + 'condition_variable' => 'required|string|max:50', + 'result_value' => 'required|string', + 'result_type' => 'in:fixed,formula', + 'sort_order' => 'nullable|integer', + ]); + + $range = $this->rangeService->createRange($formulaId, $validated); + + return response()->json([ + 'success' => true, + 'message' => '범위가 추가되었습니다.', + 'data' => $range, + ]); + } + + /** + * 범위 수정 + */ + public function update(Request $request, int $formulaId, int $rangeId): JsonResponse + { + $validated = $request->validate([ + 'min_value' => 'nullable|numeric', + 'max_value' => 'nullable|numeric', + 'result_value' => 'required|string', + 'result_type' => 'in:fixed,formula', + ]); + + $this->rangeService->updateRange($rangeId, $validated); + + return response()->json([ + 'success' => true, + 'message' => '범위가 수정되었습니다.', + ]); + } + + /** + * 범위 삭제 + */ + public function destroy(int $formulaId, int $rangeId): JsonResponse + { + $this->rangeService->deleteRange($rangeId); + + return response()->json([ + 'success' => true, + 'message' => '범위가 삭제되었습니다.', + ]); + } + + /** + * 순서 변경 + */ + public function reorder(Request $request, int $formulaId): JsonResponse + { + $validated = $request->validate([ + 'range_ids' => 'required|array', + 'range_ids.*' => 'integer', + ]); + + $this->rangeService->reorder($validated['range_ids']); + + return response()->json([ + 'success' => true, + 'message' => '순서가 변경되었습니다.', + ]); + } +} +``` + +### 10.2 Service 패턴 (MNG) + +```php +orderBy('sort_order') + ->get(); + } + + /** + * 범위 생성 + */ + public function createRange(int $formulaId, array $data): QuoteFormulaRange + { + $data['formula_id'] = $formulaId; + + // 순서 자동 설정 + if (!isset($data['sort_order'])) { + $maxOrder = QuoteFormulaRange::where('formula_id', $formulaId)->max('sort_order') ?? 0; + $data['sort_order'] = $maxOrder + 1; + } + + return QuoteFormulaRange::create($data); + } + + /** + * 범위 수정 + */ + public function updateRange(int $rangeId, array $data): QuoteFormulaRange + { + $range = QuoteFormulaRange::findOrFail($rangeId); + $range->update($data); + + return $range->fresh(); + } + + /** + * 범위 삭제 + */ + public function deleteRange(int $rangeId): void + { + QuoteFormulaRange::destroy($rangeId); + } + + /** + * 순서 변경 + */ + public function reorder(array $rangeIds): void + { + foreach ($rangeIds as $order => $id) { + QuoteFormulaRange::where('id', $id)->update(['sort_order' => $order + 1]); + } + } +} +``` + +### 10.3 Model 구조 (참조용) + +```php + 'decimal:4', + 'max_value' => 'decimal:4', + 'sort_order' => 'integer', + ]; + + public function formula(): BelongsTo + { + return $this->belongsTo(QuoteFormula::class, 'formula_id'); + } + + public function isInRange($value): bool + { + // min < value <= max 체크 + } +} +``` + +### 10.4 Blade View 패턴 (HTMX + Alpine.js) + +```blade +{{-- 파일: resources/views/quote-formulas/partials/ranges-tab.blade.php --}} + +
| # | +최소값 | +최대값 | +결과값 | +품목코드 | +액션 | +
|---|---|---|---|---|---|
| + | + | + | + + | ++ + | ++ + + | +