293 lines
8.7 KiB
Markdown
293 lines
8.7 KiB
Markdown
|
|
# 견적 시뮬레이터 계산 로직 매핑
|
|||
|
|
|
|||
|
|
> **작성일**: 2025-12-23
|
|||
|
|
> **목표**: design.sam.kr 계산 로직 → mng 시뮬레이터 동기화
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Design 계산 로직 분석
|
|||
|
|
|
|||
|
|
### 1.1 핵심 계산 변수 (AutoCalculationSimulator.tsx:429-444)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const calculationVariables = {
|
|||
|
|
W0: quote.w0, // 오픈사이즈 폭 (입력)
|
|||
|
|
H0: quote.h0, // 오픈사이즈 높이 (입력)
|
|||
|
|
W1: quote.category === '스크린' ? W0 + 140 : W0 + 110, // 제작폭
|
|||
|
|
H1: H0 + 350, // 제작높이
|
|||
|
|
W: W1, // W = W1 (매핑)
|
|||
|
|
H: H1, // H = H1 (매핑)
|
|||
|
|
M: (W1 * H1) / 1000000, // 면적 (㎡)
|
|||
|
|
K: 0 // 중량 (미구현)
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 수식 평가 함수 (formulaEvaluator.ts)
|
|||
|
|
|
|||
|
|
**지원 함수:**
|
|||
|
|
| 함수 | 설명 | 예시 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `SUM(a, b, ...)` | 합계 | `SUM(W0, H0, 100)` |
|
|||
|
|
| `AVERAGE(a, b, ...)` | 평균 | `AVERAGE(W0, H0)` |
|
|||
|
|
| `MAX(a, b, ...)` | 최대값 | `MAX(W0, 1000)` |
|
|||
|
|
| `MIN(a, b, ...)` | 최소값 | `MIN(H0, 3000)` |
|
|||
|
|
| `ROUND(value, decimals)` | 반올림 | `ROUND(M, 2)` |
|
|||
|
|
| `CEIL(value)` | 올림 | `CEIL(H1 / 1000)` |
|
|||
|
|
| `FLOOR(value)` | 내림 | `FLOOR(W1 / 500)` |
|
|||
|
|
| `ABS(value)` | 절대값 | `ABS(W0 - 2000)` |
|
|||
|
|
| `IF(cond, true, false)` | 조건문 | `IF(W0 > 3000, 2, 1)` |
|
|||
|
|
| `SQRT(value)` | 제곱근 | `SQRT(M)` |
|
|||
|
|
| `POWER(base, exp)` | 거듭제곱 | `POWER(W1, 2)` |
|
|||
|
|
|
|||
|
|
**평가 과정:**
|
|||
|
|
```typescript
|
|||
|
|
// 1. 변수 치환
|
|||
|
|
let expr = formula;
|
|||
|
|
Object.keys(vars).forEach(key => {
|
|||
|
|
const regex = new RegExp(`\\b${key}\\b`, 'g');
|
|||
|
|
expr = expr.replace(regex, vars[key].toString());
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 2. 함수 처리 (CEIL, FLOOR, ROUND 등)
|
|||
|
|
expr = processFunctions(expr);
|
|||
|
|
|
|||
|
|
// 3. 최종 계산
|
|||
|
|
return new Function(`return (${expr})`)();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.3 BOM 계산 과정 (bomCalculatorWithDebug.ts)
|
|||
|
|
|
|||
|
|
**10단계 계산 과정:**
|
|||
|
|
|
|||
|
|
| 단계 | 설명 | 예시 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| Step 1 | 수량 공식 확인 | `H/1000` |
|
|||
|
|
| Step 2 | 변수 값 확인 | `{W0:2000, H0:2500, W1:2140, H1:2850, M:6.099}` |
|
|||
|
|
| Step 3 | 수량 계산 과정 | `H/1000 = 2850/1000 = 2.85` |
|
|||
|
|
| Step 4 | 계산된 수량 | `2.85` |
|
|||
|
|
| Step 5 | 단가 소스 | `단가관리 (15,000원)` |
|
|||
|
|
| Step 6 | 기본 단가 | `15,000` |
|
|||
|
|
| Step 7 | 카테고리 승수 | `면적단가 (15,000원/㎡ × 6.099㎡)` |
|
|||
|
|
| Step 8 | 최종 단가 | `91,485` |
|
|||
|
|
| Step 9 | 금액 계산 | `2.85 × 91,485 = 260,732` |
|
|||
|
|
| Step 10 | 최종 금액 | `260,732` |
|
|||
|
|
|
|||
|
|
### 1.4 단가 계산 방식
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 1순위: pricing 테이블에서 조회
|
|||
|
|
const itemPricing = pricings.find(p => p.itemCode === bomEntry.childItemCode);
|
|||
|
|
if (itemPricing && itemPricing.salesPrice) {
|
|||
|
|
unitPrice = itemPricing.salesPrice;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2순위: 품목마스터에서 조회
|
|||
|
|
else if (childItem.salesPrice) {
|
|||
|
|
unitPrice = childItem.salesPrice;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 면적 기반 품목 판단 (원단, 패널, 도장, 표면처리)
|
|||
|
|
const areaBasedCategories = ['원단', '패널', '도장', '표면처리'];
|
|||
|
|
if (isAreaBased && M > 0) {
|
|||
|
|
finalUnitPrice = unitPrice * M; // 면적 단가
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. MNG 현재 구현 상태
|
|||
|
|
|
|||
|
|
### 2.1 FormulaEvaluatorService.php
|
|||
|
|
|
|||
|
|
**✅ 구현됨:**
|
|||
|
|
- `evaluate()` - 수식 평가
|
|||
|
|
- `validateFormula()` - 수식 검증
|
|||
|
|
- `executeAll()` - 전체 수식 실행
|
|||
|
|
- `getItemPrice()` - 단가 조회 (Price 모델 연동)
|
|||
|
|
- `getBomTree()` - BOM 트리 조회
|
|||
|
|
- `enrichItemsWithDetails()` - 품목 상세 정보 추가
|
|||
|
|
|
|||
|
|
**지원 함수:**
|
|||
|
|
- SUM, ROUND, CEIL, FLOOR, ABS, MIN, MAX, IF, AND, OR, NOT
|
|||
|
|
|
|||
|
|
**❌ 미구현:**
|
|||
|
|
- 제품 카테고리별 변수 계산 규칙 (W1, H1, M)
|
|||
|
|
- 면적/중량 기반 단가 계산
|
|||
|
|
- 공정별 분류
|
|||
|
|
|
|||
|
|
### 2.2 simulator.blade.php
|
|||
|
|
|
|||
|
|
**✅ 구현됨:**
|
|||
|
|
- 입력 변수 폼 (W0, H0, PC, GT, MP, CT 등)
|
|||
|
|
- API 호출 (`/api/admin/quote-formulas/formulas/simulate`)
|
|||
|
|
- 계산된 변수 표시
|
|||
|
|
- 품목 목록 표시 (BOM 트리 포함)
|
|||
|
|
|
|||
|
|
**❌ 미구현:**
|
|||
|
|
- 공정별 그룹화 표시
|
|||
|
|
- 단가/금액 계산 결과 표시
|
|||
|
|
- 총합계 표시
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 매핑 계획
|
|||
|
|
|
|||
|
|
### 3.1 변수 계산 규칙 추가
|
|||
|
|
|
|||
|
|
**quote_formulas 테이블에 추가할 수식:**
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 제작폭 (W1) - 스크린
|
|||
|
|
INSERT INTO quote_formulas (category_id, variable, name, type, formula, output_type, sort_order)
|
|||
|
|
VALUES (1, 'W1', '제작폭', 'calculation', 'IF(PC == "screen", W0 + 140, W0 + 110)', 'variable', 10);
|
|||
|
|
|
|||
|
|
-- 제작높이 (H1)
|
|||
|
|
INSERT INTO quote_formulas (category_id, variable, name, type, formula, output_type, sort_order)
|
|||
|
|
VALUES (1, 'H1', '제작높이', 'calculation', 'H0 + 350', 'variable', 20);
|
|||
|
|
|
|||
|
|
-- 면적 (M)
|
|||
|
|
INSERT INTO quote_formulas (category_id, variable, name, type, formula, output_type, sort_order)
|
|||
|
|
VALUES (1, 'M', '면적', 'calculation', '(W1 * H1) / 1000000', 'variable', 30);
|
|||
|
|
|
|||
|
|
-- W, H 매핑
|
|||
|
|
INSERT INTO quote_formulas (category_id, variable, name, type, formula, output_type, sort_order)
|
|||
|
|
VALUES (1, 'W', '제작폭', 'calculation', 'W1', 'variable', 40);
|
|||
|
|
INSERT INTO quote_formulas (category_id, variable, name, type, formula, output_type, sort_order)
|
|||
|
|
VALUES (1, 'H', '제작높이', 'calculation', 'H1', 'variable', 50);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 FormulaEvaluatorService 확장
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// 추가할 메서드
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 카테고리별 단가 계산
|
|||
|
|
*/
|
|||
|
|
private function calculateCategoryPrice(
|
|||
|
|
array $item,
|
|||
|
|
float $basePrice,
|
|||
|
|
array $variables
|
|||
|
|
): array {
|
|||
|
|
$areaBasedCategories = ['원단', '패널', '도장', '표면처리'];
|
|||
|
|
$itemCategory = $item['item_category'] ?? '';
|
|||
|
|
|
|||
|
|
if (in_array($itemCategory, $areaBasedCategories) && isset($variables['M'])) {
|
|||
|
|
return [
|
|||
|
|
'final_price' => $basePrice * $variables['M'],
|
|||
|
|
'calculation_note' => "면적단가 ({$basePrice}원/㎡ × {$variables['M']}㎡)"
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
'final_price' => $basePrice,
|
|||
|
|
'calculation_note' => '수량단가'
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 공정별 품목 그룹화
|
|||
|
|
*/
|
|||
|
|
private function groupItemsByProcess(array $items): array
|
|||
|
|
{
|
|||
|
|
$processOrder = ['screen' => '스크린 공정', 'bending' => '절곡 공정', 'electric' => '전기 공정'];
|
|||
|
|
$grouped = [];
|
|||
|
|
|
|||
|
|
foreach ($processOrder as $code => $label) {
|
|||
|
|
$grouped[$code] = [
|
|||
|
|
'label' => $label,
|
|||
|
|
'items' => [],
|
|||
|
|
'subtotal' => 0
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($items as $item) {
|
|||
|
|
$process = $item['process_type'] ?? 'etc';
|
|||
|
|
if (isset($grouped[$process])) {
|
|||
|
|
$grouped[$process]['items'][] = $item;
|
|||
|
|
$grouped[$process]['subtotal'] += $item['total_price'] ?? 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $grouped;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 items 테이블 확장
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 공정 유형 필드 추가
|
|||
|
|
ALTER TABLE items ADD COLUMN process_type VARCHAR(20) DEFAULT NULL
|
|||
|
|
COMMENT '공정유형: screen(스크린), bending(절곡), electric(전기)';
|
|||
|
|
|
|||
|
|
-- 인덱스 추가
|
|||
|
|
CREATE INDEX idx_items_process_type ON items(process_type);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 구현 순서
|
|||
|
|
|
|||
|
|
### Phase 1: DB 스키마 및 데이터 (1일)
|
|||
|
|
|
|||
|
|
1. ✅ items 테이블에 `process_type` 필드 추가
|
|||
|
|
2. ✅ 기존 품목에 공정 유형 매핑
|
|||
|
|
3. ✅ quote_formulas에 기본 변수 계산 수식 추가
|
|||
|
|
|
|||
|
|
### Phase 2: 백엔드 로직 (2일)
|
|||
|
|
|
|||
|
|
1. ✅ FormulaEvaluatorService에 카테고리별 단가 계산 추가
|
|||
|
|
2. ✅ 공정별 그룹화 메서드 추가
|
|||
|
|
3. ✅ executeAll() 반환 구조 확장
|
|||
|
|
|
|||
|
|
### Phase 3: 프론트엔드 (1일)
|
|||
|
|
|
|||
|
|
1. ✅ simulator.blade.php에 공정별 결과 표시
|
|||
|
|
2. ✅ 단가/금액 표시 추가
|
|||
|
|
3. ✅ 총합계 표시
|
|||
|
|
|
|||
|
|
### Phase 4: 검증 (1일)
|
|||
|
|
|
|||
|
|
1. ✅ design.sam.kr과 결과 비교
|
|||
|
|
2. ✅ 금액 차이 분석 및 조정
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 핵심 파일 참조
|
|||
|
|
|
|||
|
|
### Design (참조용)
|
|||
|
|
```
|
|||
|
|
/SAM/design/src/components/
|
|||
|
|
├── AutoCalculationSimulator.tsx # 메인 시뮬레이터 (1068줄)
|
|||
|
|
├── utils/
|
|||
|
|
│ ├── formulaEvaluator.ts # 수식 평가 (312줄)
|
|||
|
|
│ └── bomCalculatorWithDebug.ts # BOM 계산 (232줄)
|
|||
|
|
└── contexts/
|
|||
|
|
└── DataContext.tsx # 마스터 데이터 (9859줄)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### MNG (수정 대상)
|
|||
|
|
```
|
|||
|
|
/SAM/mng/
|
|||
|
|
├── app/Services/Quote/
|
|||
|
|
│ └── FormulaEvaluatorService.php # 핵심 서비스 (575줄)
|
|||
|
|
├── resources/views/quote-formulas/
|
|||
|
|
│ └── simulator.blade.php # 시뮬레이터 UI (867줄)
|
|||
|
|
└── app/Models/
|
|||
|
|
├── Price.php # 단가 모델 (135줄)
|
|||
|
|
└── Item.php # 품목 모델
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 테스트 케이스
|
|||
|
|
|
|||
|
|
| 입력 | Design 결과 | MNG 목표 |
|
|||
|
|
|------|------------|----------|
|
|||
|
|
| W0=2000, H0=2500, PC=스크린 | W1=2140, H1=2850, M=6.099 | 동일 |
|
|||
|
|
| 스크린 원단 | 수량공식: W*H/1000000 = 6.099㎡ | 동일 |
|
|||
|
|
| 가이드레일 | 수량공식: H/1000 = 2.85m | 동일 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*이 문서는 design.sam.kr 분석 결과를 바탕으로 mng 시뮬레이터 구현 계획을 정리합니다.*
|