2025-12-24 16:11:44 +09:00
|
|
|
|
# 견적 시뮬레이터 완전 동기화 계획
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-30 14:46:26 +09:00
|
|
|
|
> **작성일**: 2025-12-23 (업데이트: 2025-12-30)
|
2025-12-24 16:11:44 +09:00
|
|
|
|
> **목표**: design.sam.kr 시뮬레이터와 mng 시뮬레이터가 **동일한 결과**를 출력하도록 완전 동기화
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
## 1. Design 시스템 전체 분석
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 1.1 핵심 파일 구조
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| 파일 | 줄 수 | 역할 |
|
|
|
|
|
|
|------|-------|------|
|
|
|
|
|
|
| `AutoCalculationSimulator.tsx` | 1,068 | 메인 시뮬레이터 UI + 계산 로직 |
|
|
|
|
|
|
| `formulaEvaluator.ts` | 312 | 수식 평가 엔진 |
|
|
|
|
|
|
| `bomCalculatorWithDebug.ts` | 232 | BOM 계산 + 10단계 디버깅 |
|
|
|
|
|
|
| `DataContext.tsx` | 9,859 | 마스터 데이터 타입 + 상태 관리 |
|
|
|
|
|
|
| `sampleQuoteData_Complete.ts` | 600+ | 샘플 품목 데이터 |
|
|
|
|
|
|
| `addProductBoms.ts` | 298 | 완제품 BOM 구성 |
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 데이터 구조 (TypeScript 인터페이스)
|
|
|
|
|
|
|
|
|
|
|
|
#### 품목 마스터 (ItemMaster)
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface ItemMaster {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
itemCode: string; // 품목코드
|
|
|
|
|
|
itemName: string; // 품목명
|
|
|
|
|
|
itemType: 'FG' | 'SF' | 'PT' | 'SM' | 'RM' | 'CS';
|
|
|
|
|
|
productCategory?: 'SCREEN' | 'STEEL'; // 제품 카테고리
|
|
|
|
|
|
partType?: 'ASSEMBLY' | 'BENDING' | 'PURCHASED';
|
|
|
|
|
|
unit: string;
|
|
|
|
|
|
salesPrice?: number; // 판매단가
|
|
|
|
|
|
purchasePrice?: number; // 매입단가
|
|
|
|
|
|
marginRate?: number; // 마진율
|
|
|
|
|
|
bom?: BOMLine[]; // 하위 BOM 목록
|
|
|
|
|
|
// ... 기타 필드
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### BOM 라인 (BOMLine)
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface BOMLine {
|
|
|
|
|
|
childItemCode: string; // 자식 품목 코드
|
|
|
|
|
|
childItemName: string; // 자식 품목명
|
|
|
|
|
|
quantity: number; // 기준 수량
|
|
|
|
|
|
unit: string; // 단위
|
|
|
|
|
|
quantityFormula?: string; // 수량 수식 (예: "W*H/1000000", "H/1000")
|
|
|
|
|
|
note?: string; // 비고
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 단가 관리 (PricingData)
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```typescript
|
2025-12-24 16:11:44 +09:00
|
|
|
|
interface PricingData {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
itemId: string;
|
|
|
|
|
|
itemCode: string;
|
|
|
|
|
|
purchasePrice?: number; // 매입단가
|
|
|
|
|
|
processingCost?: number; // 가공비
|
|
|
|
|
|
loss?: number; // LOSS(%)
|
|
|
|
|
|
marginRate?: number; // 마진율
|
|
|
|
|
|
salesPrice?: number; // 판매단가
|
|
|
|
|
|
effectiveDate: string; // 적용일
|
|
|
|
|
|
status: 'draft' | 'active' | 'inactive' | 'finalized';
|
|
|
|
|
|
}
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
#### 카테고리 그룹 (CategoryGroup) - MNG에 누락
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface CategoryGroup {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string; // "면적기반", "중량기반", "수량기반"
|
|
|
|
|
|
categories: string[]; // 소속 카테고리들
|
|
|
|
|
|
multiplierVariable?: string; // 곱할 변수 (M, K 등)
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 1.3 계산 변수 체계
|
|
|
|
|
|
|
|
|
|
|
|
| 변수 | 설명 | 계산식 |
|
|
|
|
|
|
|------|------|--------|
|
|
|
|
|
|
| `W0` | 오픈사이즈 폭 | 사용자 입력 |
|
|
|
|
|
|
| `H0` | 오픈사이즈 높이 | 사용자 입력 |
|
|
|
|
|
|
| `PC` | 제품 카테고리 | "스크린" / "철재" |
|
|
|
|
|
|
| `W1` | 제작폭 | PC=="스크린" ? W0+140 : W0+110 |
|
|
|
|
|
|
| `H1` | 제작높이 | H0 + 350 |
|
|
|
|
|
|
| `W` | 제작폭 (별칭) | = W1 |
|
|
|
|
|
|
| `H` | 제작높이 (별칭) | = H1 |
|
|
|
|
|
|
| `M` | 면적 (㎡) | (W1 × H1) / 1,000,000 |
|
|
|
|
|
|
| `K` | 중량 (kg) | 스크린: M×2 + W0/1000×14.17, 철재: M×25 |
|
|
|
|
|
|
| `GT` | 가이드레일 설치유형 | "벽면형" / "측면형" |
|
|
|
|
|
|
| `MP` | 모터 전원 | "220V" / "380V" |
|
|
|
|
|
|
| `CT` | 연동제어기 | "단독" / "연동" |
|
|
|
|
|
|
| `QTY` | 수량 | 사용자 입력 |
|
|
|
|
|
|
|
|
|
|
|
|
### 1.4 수식 평가 함수
|
|
|
|
|
|
|
|
|
|
|
|
**지원 함수 목록:**
|
2025-12-23 23:41:47 +09:00
|
|
|
|
| 함수 | 설명 | 예시 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `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)` |
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| `ROUND(val, dec)` | 반올림 | `ROUND(M, 2)` |
|
|
|
|
|
|
| `CEIL(val)` | 올림 | `CEIL(H1 / 1000)` |
|
|
|
|
|
|
| `FLOOR(val)` | 내림 | `FLOOR(W1 / 500)` |
|
|
|
|
|
|
| `ABS(val)` | 절대값 | `ABS(W0 - 2000)` |
|
|
|
|
|
|
| `IF(cond, t, f)` | 조건문 | `IF(W0 > 3000, 2, 1)` |
|
|
|
|
|
|
| `SQRT(val)` | 제곱근 | `SQRT(M)` |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
| `POWER(base, exp)` | 거듭제곱 | `POWER(W1, 2)` |
|
|
|
|
|
|
|
|
|
|
|
|
**평가 과정:**
|
|
|
|
|
|
```typescript
|
2025-12-24 16:11:44 +09:00
|
|
|
|
// 1. 변수 치환 (긴 변수명부터)
|
|
|
|
|
|
const sortedVars = Object.keys(vars).sort((a, b) => b.length - a.length);
|
|
|
|
|
|
sortedVars.forEach(varName => {
|
|
|
|
|
|
const regex = new RegExp(`\\b${varName}\\b`, 'g');
|
|
|
|
|
|
formula = formula.replace(regex, String(vars[varName]));
|
2025-12-23 23:41:47 +09:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 함수 처리 (CEIL, FLOOR, ROUND 등)
|
2025-12-24 16:11:44 +09:00
|
|
|
|
formula = processFunctions(formula);
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
// 3. 최종 계산
|
2025-12-24 16:11:44 +09:00
|
|
|
|
return new Function(`return (${formula})`)();
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 1.5 BOM 계산 10단계 프로세스
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| 단계 | 항목 | 예시 |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 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` |
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| Step 5 | 단가 소스 | `단가관리 (15,000원)` 또는 `품목마스터 (15,000원)` |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
| 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` |
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 1.6 단가 계산 로직
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-24 16:11:44 +09:00
|
|
|
|
// 1. 단가 조회 우선순위
|
|
|
|
|
|
let unitPrice = 0;
|
|
|
|
|
|
let priceSource = '단가 없음';
|
|
|
|
|
|
|
2025-12-23 23:41:47 +09:00
|
|
|
|
// 1순위: pricing 테이블에서 조회
|
|
|
|
|
|
const itemPricing = pricings.find(p => p.itemCode === bomEntry.childItemCode);
|
|
|
|
|
|
if (itemPricing && itemPricing.salesPrice) {
|
|
|
|
|
|
unitPrice = itemPricing.salesPrice;
|
2025-12-24 16:11:44 +09:00
|
|
|
|
priceSource = `단가관리 (${unitPrice.toLocaleString()}원)`;
|
2025-12-23 23:41:47 +09:00
|
|
|
|
}
|
|
|
|
|
|
// 2순위: 품목마스터에서 조회
|
|
|
|
|
|
else if (childItem.salesPrice) {
|
|
|
|
|
|
unitPrice = childItem.salesPrice;
|
2025-12-24 16:11:44 +09:00
|
|
|
|
priceSource = `품목마스터 (${unitPrice.toLocaleString()}원)`;
|
2025-12-23 23:41:47 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
// 2. 면적 기반 품목 판단
|
2025-12-23 23:41:47 +09:00
|
|
|
|
const areaBasedCategories = ['원단', '패널', '도장', '표면처리'];
|
2025-12-24 16:11:44 +09:00
|
|
|
|
const isAreaBased = areaBasedCategories.some(cat =>
|
|
|
|
|
|
itemCategory.includes(cat) || childItem.itemName.includes(cat)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 최종 단가 계산
|
|
|
|
|
|
let finalUnitPrice = unitPrice;
|
|
|
|
|
|
if (isAreaBased && calculationVariables.M > 0) {
|
|
|
|
|
|
finalUnitPrice = unitPrice * calculationVariables.M; // 면적 단가
|
|
|
|
|
|
priceCalculationNote = `면적단가 (${unitPrice}원/㎡ × ${M}㎡)`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
priceCalculationNote = '수량단가';
|
2025-12-23 23:41:47 +09:00
|
|
|
|
}
|
2025-12-24 16:11:44 +09:00
|
|
|
|
|
|
|
|
|
|
// 4. 최종 금액
|
|
|
|
|
|
const totalPrice = calculatedQuantity * finalUnitPrice;
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
## 2. Design 샘플 데이터 분석
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 2.1 품목 구성 (약 100개)
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| 유형 | 코드 접두사 | 수량 | 설명 |
|
|
|
|
|
|
|------|------------|------|------|
|
|
|
|
|
|
| 원자재 (RM) | RM-* | 20 | 강판, 알루미늄, 원단, 패킹 등 |
|
|
|
|
|
|
| 부자재 (SM) | SM-* | 25 | 볼트, 너트, 전선, 실리콘 등 |
|
|
|
|
|
|
| 스크린 반제품 (SF) | SF-SCR-* | 20 | 원단, 가이드레일, 케이스, 모터 등 |
|
|
|
|
|
|
| 철재 반제품 (SF) | SF-STL-*, SF-BND-* | 20 | 도어, 프레임, 패널, 절곡 부품 등 |
|
|
|
|
|
|
| 스크린 완제품 (FG) | FG-SCR-* | 5 | 소형/중형/대형/특대/맞춤형 |
|
|
|
|
|
|
| 철재 완제품 (FG) | FG-STL-* | 5 | 소형/중형/대형/양개문/특수 |
|
|
|
|
|
|
| 절곡 완제품 (FG) | FG-BND-* | 4 | L형/U형/Z형/ㄷ형 |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 2.2 주요 BOM 수식 패턴
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| 품목 유형 | 수식 | 설명 |
|
|
|
|
|
|
|----------|------|------|
|
|
|
|
|
|
| 스크린 원단 | `W*H/1000000` | 면적 계산 |
|
|
|
|
|
|
| 가이드레일 | `H/1000` | 높이(m) 기준 |
|
|
|
|
|
|
| 엣지윙 | `H/1000` | 높이(m) 기준 |
|
|
|
|
|
|
| 철재 프레임 | `(W+H)*2/1000` | 둘레(m) 기준 |
|
|
|
|
|
|
| 철재 패널 | `W*H/1000000` | 면적 계산 |
|
|
|
|
|
|
| 실링재 | `(W+H)*2/1000` | 둘레(m) 기준 |
|
|
|
|
|
|
| 파우더 도장 | `W*H/1000000` | 면적 계산 |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 2.3 완제품 BOM 예시 (FG-SCR-002 중형 스크린)
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
```typescript
|
|
|
|
|
|
{
|
|
|
|
|
|
itemCode: 'FG-SCR-002',
|
|
|
|
|
|
itemName: '방화스크린 중형 (2000x3000)',
|
|
|
|
|
|
bom: [
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-F01', quantity: 6.0, unit: 'M2', quantityFormula: 'W*H/1000000' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-F02', quantity: 3.0, unit: 'M', quantityFormula: 'H/1000' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-F03', quantity: 3.0, unit: 'M', quantityFormula: 'H/1000' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-F04', quantity: 1, unit: 'EA' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-F05', quantity: 1, unit: 'EA' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-M02', quantity: 1, unit: 'EA', note: '중형용' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-C01', quantity: 1, unit: 'EA' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-S01', quantity: 1, unit: 'SET' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-W01', quantity: 1, unit: 'EA' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-B01', quantity: 2, unit: 'SET', note: '중형용 2세트' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-E01', quantity: 3.0, unit: 'M', quantityFormula: 'H/1000' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-E02', quantity: 3.0, unit: 'M', quantityFormula: 'H/1000' },
|
|
|
|
|
|
{ childItemCode: 'SF-SCR-REM01', quantity: 1, unit: 'EA' },
|
|
|
|
|
|
{ childItemCode: 'SM-B002', quantity: 30, unit: 'EA', note: '조립용' },
|
|
|
|
|
|
{ childItemCode: 'SM-N002', quantity: 30, unit: 'EA' },
|
|
|
|
|
|
{ childItemCode: 'SM-A001', quantity: 8, unit: 'EA', note: '고정용' },
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. MNG 현재 상태 분석
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 테이블 구조
|
|
|
|
|
|
|
|
|
|
|
|
| 테이블 | 현재 상태 | Design 대응 |
|
|
|
|
|
|
|--------|----------|-------------|
|
|
|
|
|
|
| `items` | 364개 (RM:133, SM:217, PT:6, FG:3, CS:5) | ItemMaster |
|
|
|
|
|
|
| `item_details` | 품목 상세 정보 | ItemMaster 확장 필드 |
|
|
|
|
|
|
| `prices` | 3개 (거의 없음) | PricingData |
|
|
|
|
|
|
| `quote_formulas` | 57개 (기본 변수 있음) | FormulaRule, CalculationFormula |
|
|
|
|
|
|
| `quote_formula_ranges` | 범위 규칙 | FormulaRule.ranges |
|
|
|
|
|
|
| `quote_formula_items` | 수식 품목 매핑 | BOM 연동 |
|
|
|
|
|
|
| `common_codes` | 코드 그룹 | CategoryGroup (부분) |
|
|
|
|
|
|
| `category_groups` | ❌ 없음 | CategoryGroup 추가 필요 |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### 3.2 quote_formulas 현재 데이터 (샘플)
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
[PC] 제품카테고리 (input) => variable
|
|
|
|
|
|
[W0] 가로 (W0) (input) => variable
|
|
|
|
|
|
[H0] 세로 (H0) (input) => variable
|
|
|
|
|
|
[W1_SCREEN] 제작사이즈 W1 (스크린): W0 + 140 => variable
|
|
|
|
|
|
[H1_SCREEN] 제작사이즈 H1 (스크린): H0 + 350 => variable
|
|
|
|
|
|
[W1_STEEL] 제작사이즈 W1 (철재): W0 + 110 => variable
|
|
|
|
|
|
[H1_STEEL] 제작사이즈 H1 (철재): H0 + 350 => variable
|
|
|
|
|
|
[M] 면적 계산: W1 * H1 / 1000000 => variable
|
|
|
|
|
|
[K_SCREEN] 중량 계산 (스크린): M * 2 + W0 / 1000 * 14.17 => variable
|
|
|
|
|
|
[K_STEEL] 중량 계산 (철재): M * 25 => variable
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 누락 항목
|
|
|
|
|
|
|
|
|
|
|
|
| 항목 | 설명 | 우선순위 |
|
|
|
|
|
|
|------|------|---------|
|
|
|
|
|
|
| `items.process_type` | 공정유형 (스크린/절곡/전기) | 높음 |
|
|
|
|
|
|
| `items.item_category` | 품목분류 (원단/패널/도장 등) | 높음 |
|
|
|
|
|
|
| `category_groups` 테이블 | 면적/중량 기반 분류 | 높음 |
|
|
|
|
|
|
| Design 샘플 품목 데이터 | 100개 품목 Seeder | 높음 |
|
|
|
|
|
|
| BOM 구성 데이터 | 제품별 BOM Seeder | 높음 |
|
|
|
|
|
|
| 단가 데이터 | 품목별 단가 Seeder | 중간 |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
## 4. 완전 동기화 구현 계획
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 1: DB 스키마 확장 (1일)
|
|
|
|
|
|
|
|
|
|
|
|
#### 1.1 items 테이블 필드 추가
|
|
|
|
|
|
```sql
|
|
|
|
|
|
ALTER TABLE items ADD COLUMN process_type VARCHAR(20) DEFAULT NULL
|
|
|
|
|
|
COMMENT '공정유형: screen(스크린), bending(절곡), electric(전기), steel(철재)';
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
ALTER TABLE items ADD COLUMN item_category VARCHAR(50) DEFAULT NULL
|
|
|
|
|
|
COMMENT '품목분류: 원단, 패널, 도장, 표면처리, 가이드레일, 케이스, 모터, 제어반 등';
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
CREATE INDEX idx_items_process_type ON items(process_type);
|
|
|
|
|
|
CREATE INDEX idx_items_item_category ON items(item_category);
|
|
|
|
|
|
```
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
#### 1.2 category_groups 테이블 생성
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```sql
|
2025-12-24 16:11:44 +09:00
|
|
|
|
CREATE TABLE category_groups (
|
|
|
|
|
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
|
|
|
|
|
code VARCHAR(50) NOT NULL COMMENT '코드: area_based, weight_based, quantity_based',
|
|
|
|
|
|
name VARCHAR(100) NOT NULL COMMENT '이름: 면적기반, 중량기반, 수량기반',
|
|
|
|
|
|
multiplier_variable VARCHAR(20) COMMENT '곱셈 변수: M, K, null',
|
|
|
|
|
|
categories JSON COMMENT '소속 카테고리 목록',
|
|
|
|
|
|
description TEXT,
|
|
|
|
|
|
sort_order INT DEFAULT 0,
|
|
|
|
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
|
|
|
|
|
|
|
INDEX idx_tenant (tenant_id),
|
|
|
|
|
|
INDEX idx_code (code)
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 2: Seeder 작성 (2일)
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1 품목 마스터 Seeder
|
|
|
|
|
|
|
|
|
|
|
|
**파일**: `database/seeders/DesignItemSeeder.php`
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
|
class DesignItemSeeder extends Seeder
|
|
|
|
|
|
{
|
|
|
|
|
|
public function run(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// 원자재 (20개)
|
|
|
|
|
|
$rawMaterials = [
|
|
|
|
|
|
['code' => 'RM-S001', 'name' => '강판 1.2T', 'unit' => 'KG', 'price' => 3500, 'category' => '강판'],
|
|
|
|
|
|
['code' => 'RM-F001', 'name' => '방화원단 A급', 'unit' => 'M2', 'price' => 28000, 'category' => '원단'],
|
|
|
|
|
|
// ... 18개 더
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 부자재 (25개)
|
|
|
|
|
|
$subMaterials = [
|
|
|
|
|
|
['code' => 'SM-B001', 'name' => '볼트 M8x30', 'unit' => 'EA', 'price' => 150, 'category' => '볼트'],
|
|
|
|
|
|
// ... 24개 더
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 스크린 반제품 (20개)
|
|
|
|
|
|
$screenSemiProducts = [
|
|
|
|
|
|
['code' => 'SF-SCR-F01', 'name' => '스크린 원단', 'unit' => 'M2', 'price' => 35000, 'category' => '원단', 'process' => 'screen'],
|
|
|
|
|
|
['code' => 'SF-SCR-F02', 'name' => '가이드레일 (좌)', 'unit' => 'M', 'price' => 42000, 'category' => '가이드레일', 'process' => 'screen'],
|
|
|
|
|
|
// ... 18개 더
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 완제품 (14개)
|
|
|
|
|
|
$finishedProducts = [
|
|
|
|
|
|
['code' => 'FG-SCR-001', 'name' => '방화스크린 소형', 'category' => 'SCREEN'],
|
|
|
|
|
|
['code' => 'FG-SCR-002', 'name' => '방화스크린 중형', 'category' => 'SCREEN'],
|
|
|
|
|
|
// ... 12개 더
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
#### 2.2 BOM 구성 Seeder
|
|
|
|
|
|
|
|
|
|
|
|
**파일**: `database/seeders/DesignBomSeeder.php`
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
|
class DesignBomSeeder extends Seeder
|
|
|
|
|
|
{
|
|
|
|
|
|
public function run(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$bomData = [
|
|
|
|
|
|
'FG-SCR-002' => [
|
|
|
|
|
|
['child' => 'SF-SCR-F01', 'qty' => 1, 'formula' => 'W*H/1000000', 'unit' => 'M2'],
|
|
|
|
|
|
['child' => 'SF-SCR-F02', 'qty' => 1, 'formula' => 'H/1000', 'unit' => 'M'],
|
|
|
|
|
|
['child' => 'SF-SCR-F03', 'qty' => 1, 'formula' => 'H/1000', 'unit' => 'M'],
|
|
|
|
|
|
['child' => 'SF-SCR-F04', 'qty' => 1, 'formula' => '', 'unit' => 'EA'],
|
|
|
|
|
|
// ... 더 많은 BOM 라인
|
|
|
|
|
|
],
|
|
|
|
|
|
// ... 다른 제품들
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.3 CategoryGroup Seeder
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
```php
|
2025-12-24 16:11:44 +09:00
|
|
|
|
class CategoryGroupSeeder extends Seeder
|
|
|
|
|
|
{
|
|
|
|
|
|
public function run(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$groups = [
|
|
|
|
|
|
[
|
|
|
|
|
|
'code' => 'area_based',
|
|
|
|
|
|
'name' => '면적기반',
|
|
|
|
|
|
'multiplier_variable' => 'M',
|
|
|
|
|
|
'categories' => json_encode(['원단', '패널', '도장', '표면처리']),
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'code' => 'weight_based',
|
|
|
|
|
|
'name' => '중량기반',
|
|
|
|
|
|
'multiplier_variable' => 'K',
|
|
|
|
|
|
'categories' => json_encode(['강판', '알루미늄']),
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'code' => 'quantity_based',
|
|
|
|
|
|
'name' => '수량기반',
|
|
|
|
|
|
'multiplier_variable' => null,
|
|
|
|
|
|
'categories' => json_encode(['볼트', '너트', '모터', '제어반']),
|
|
|
|
|
|
],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### Phase 3: 백엔드 로직 확장 (2일)
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.1 FormulaEvaluatorService 확장
|
|
|
|
|
|
|
|
|
|
|
|
**추가할 메서드:**
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
2025-12-23 23:41:47 +09:00
|
|
|
|
/**
|
2025-12-24 16:11:44 +09:00
|
|
|
|
* 카테고리 기반 단가 계산
|
2025-12-23 23:41:47 +09:00
|
|
|
|
*/
|
|
|
|
|
|
private function calculateCategoryPrice(
|
|
|
|
|
|
array $item,
|
|
|
|
|
|
float $basePrice,
|
|
|
|
|
|
array $variables
|
|
|
|
|
|
): array {
|
2025-12-24 16:11:44 +09:00
|
|
|
|
$categoryGroup = CategoryGroup::query()
|
|
|
|
|
|
->whereJsonContains('categories', $item['item_category'] ?? '')
|
|
|
|
|
|
->first();
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
if (!$categoryGroup || !$categoryGroup->multiplier_variable) {
|
2025-12-23 23:41:47 +09:00
|
|
|
|
return [
|
2025-12-24 16:11:44 +09:00
|
|
|
|
'final_price' => $basePrice,
|
|
|
|
|
|
'calculation_note' => '수량단가',
|
|
|
|
|
|
'multiplier' => 1,
|
2025-12-23 23:41:47 +09:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
$multiplierVar = $categoryGroup->multiplier_variable;
|
|
|
|
|
|
$multiplierValue = $variables[$multiplierVar] ?? 1;
|
|
|
|
|
|
|
2025-12-23 23:41:47 +09:00
|
|
|
|
return [
|
2025-12-24 16:11:44 +09:00
|
|
|
|
'final_price' => $basePrice * $multiplierValue,
|
|
|
|
|
|
'calculation_note' => "{$categoryGroup->name} ({$basePrice}원/{$this->getUnit($multiplierVar)} × {$multiplierValue})",
|
|
|
|
|
|
'multiplier' => $multiplierValue,
|
2025-12-23 23:41:47 +09:00
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 공정별 품목 그룹화
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function groupItemsByProcess(array $items): array
|
|
|
|
|
|
{
|
2025-12-24 16:11:44 +09:00
|
|
|
|
$processOrder = [
|
|
|
|
|
|
'screen' => ['label' => '스크린 공정', 'items' => [], 'subtotal' => 0],
|
|
|
|
|
|
'bending' => ['label' => '절곡 공정', 'items' => [], 'subtotal' => 0],
|
|
|
|
|
|
'electric' => ['label' => '전기 공정', 'items' => [], 'subtotal' => 0],
|
|
|
|
|
|
'assembly' => ['label' => '조립 공정', 'items' => [], 'subtotal' => 0],
|
|
|
|
|
|
'etc' => ['label' => '기타', 'items' => [], 'subtotal' => 0],
|
|
|
|
|
|
];
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
foreach ($items as $item) {
|
|
|
|
|
|
$process = $item['process_type'] ?? 'etc';
|
2025-12-24 16:11:44 +09:00
|
|
|
|
if (isset($processOrder[$process])) {
|
|
|
|
|
|
$processOrder[$process]['items'][] = $item;
|
|
|
|
|
|
$processOrder[$process]['subtotal'] += $item['total_price'] ?? 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$processOrder['etc']['items'][] = $item;
|
|
|
|
|
|
$processOrder['etc']['subtotal'] += $item['total_price'] ?? 0;
|
2025-12-23 23:41:47 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
return array_filter($processOrder, fn($g) => count($g['items']) > 0);
|
2025-12-23 23:41:47 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
/**
|
|
|
|
|
|
* 10단계 디버깅 정보 생성
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function generateDebugInfo(
|
|
|
|
|
|
array $bomLine,
|
|
|
|
|
|
array $variables,
|
|
|
|
|
|
float $calculatedQty,
|
|
|
|
|
|
float $basePrice,
|
|
|
|
|
|
float $finalPrice,
|
|
|
|
|
|
float $totalPrice,
|
|
|
|
|
|
string $priceSource,
|
|
|
|
|
|
string $calcNote
|
|
|
|
|
|
): array {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'step1_formula' => $bomLine['quantity_formula'] ?? '수식 없음',
|
|
|
|
|
|
'step2_variables' => $variables,
|
|
|
|
|
|
'step3_quantity_calc' => $this->buildQuantityCalcString($bomLine, $variables, $calculatedQty),
|
|
|
|
|
|
'step4_quantity' => $calculatedQty,
|
|
|
|
|
|
'step5_price_source' => $priceSource,
|
|
|
|
|
|
'step6_base_price' => $basePrice,
|
|
|
|
|
|
'step7_category_multiplier' => $calcNote,
|
|
|
|
|
|
'step8_final_price' => $finalPrice,
|
|
|
|
|
|
'step9_total_calc' => sprintf('%.2f × %s = %s', $calculatedQty, number_format($finalPrice), number_format($totalPrice)),
|
|
|
|
|
|
'step10_total' => $totalPrice,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
#### 3.2 executeAll() 반환 구조 확장
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
```php
|
|
|
|
|
|
public function executeAll(array $inputVariables): array
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 변수 계산
|
|
|
|
|
|
$calculatedVariables = $this->calculateVariables($inputVariables);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 제품 BOM 조회
|
|
|
|
|
|
$product = Item::where('code', $inputVariables['PRODUCT_ID'])->first();
|
|
|
|
|
|
$bomTree = $this->getBomTree($product);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. BOM 항목별 계산
|
|
|
|
|
|
$bomItems = [];
|
|
|
|
|
|
foreach ($bomTree as $bomLine) {
|
|
|
|
|
|
$result = $this->calculateBomItem($bomLine, $calculatedVariables);
|
|
|
|
|
|
$bomItems[] = $result;
|
|
|
|
|
|
}
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
// 4. 공정별 그룹화
|
|
|
|
|
|
$groupedByProcess = $this->groupItemsByProcess($bomItems);
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
// 5. 총합계
|
|
|
|
|
|
$totalAmount = array_sum(array_column($bomItems, 'total_price'));
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
return [
|
|
|
|
|
|
'input_variables' => $inputVariables,
|
|
|
|
|
|
'calculated_variables' => $calculatedVariables,
|
|
|
|
|
|
'product' => [
|
|
|
|
|
|
'code' => $product->code,
|
|
|
|
|
|
'name' => $product->name,
|
|
|
|
|
|
'category' => $product->item_details->product_category ?? null,
|
|
|
|
|
|
],
|
|
|
|
|
|
'bom_items' => $bomItems,
|
|
|
|
|
|
'grouped_by_process' => $groupedByProcess,
|
|
|
|
|
|
'summary' => [
|
|
|
|
|
|
'total_items' => count($bomItems),
|
|
|
|
|
|
'total_amount' => $totalAmount,
|
|
|
|
|
|
],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 4: 프론트엔드 확장 (1일)
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.1 simulator.blade.php 결과 표시 개선
|
|
|
|
|
|
|
|
|
|
|
|
```blade
|
|
|
|
|
|
{{-- 공정별 그룹화 결과 --}}
|
|
|
|
|
|
@if(isset($result['grouped_by_process']))
|
|
|
|
|
|
<div class="space-y-6">
|
|
|
|
|
|
@foreach($result['grouped_by_process'] as $processCode => $group)
|
|
|
|
|
|
<div class="border rounded-lg">
|
|
|
|
|
|
<div class="bg-gray-50 px-4 py-2 border-b flex justify-between">
|
|
|
|
|
|
<h4 class="font-semibold">{{ $group['label'] }}</h4>
|
|
|
|
|
|
<span class="text-blue-600 font-medium">
|
|
|
|
|
|
소계: {{ number_format($group['subtotal']) }}원
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<table class="w-full">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr class="bg-gray-100">
|
|
|
|
|
|
<th>품목코드</th>
|
|
|
|
|
|
<th>품목명</th>
|
|
|
|
|
|
<th>수량</th>
|
|
|
|
|
|
<th>단위</th>
|
|
|
|
|
|
<th>단가</th>
|
|
|
|
|
|
<th>금액</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
@foreach($group['items'] as $item)
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>{{ $item['item_code'] }}</td>
|
|
|
|
|
|
<td>{{ $item['item_name'] }}</td>
|
|
|
|
|
|
<td>{{ number_format($item['calculated_quantity'], 2) }}</td>
|
|
|
|
|
|
<td>{{ $item['unit'] }}</td>
|
|
|
|
|
|
<td>{{ number_format($item['final_price']) }}</td>
|
|
|
|
|
|
<td>{{ number_format($item['total_price']) }}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
@endforeach
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
@endforeach
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 총합계 --}}
|
|
|
|
|
|
<div class="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
|
<span class="text-lg font-semibold text-blue-900">총 합계</span>
|
|
|
|
|
|
<span class="text-2xl font-bold text-blue-700">
|
|
|
|
|
|
{{ number_format($result['summary']['total_amount']) }}원
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
```
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### Phase 5: 검증 및 동기화 (1일)
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
#### 5.1 테스트 케이스
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| 입력값 | Design 결과 | MNG 목표 |
|
|
|
|
|
|
|--------|------------|----------|
|
|
|
|
|
|
| W0=2000, H0=2500, PC=스크린 | W1=2140, H1=2850, M=6.099 | 동일 |
|
|
|
|
|
|
| 스크린 원단 (면적단가) | 35,000 × 6.099 = 213,465원 | 동일 |
|
|
|
|
|
|
| 가이드레일 (길이단가) | 42,000 × 2.85 = 119,700원 | 동일 |
|
|
|
|
|
|
| 모터 (고정단가) | 480,000 × 1 = 480,000원 | 동일 |
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
#### 5.2 검증 스크립트
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
```php
|
|
|
|
|
|
// php artisan tinker
|
|
|
|
|
|
|
|
|
|
|
|
// 동일 입력으로 계산 비교
|
|
|
|
|
|
$input = [
|
|
|
|
|
|
'PC' => '스크린',
|
|
|
|
|
|
'PRODUCT_ID' => 'FG-SCR-002',
|
|
|
|
|
|
'W0' => 2000,
|
|
|
|
|
|
'H0' => 2500,
|
|
|
|
|
|
'GT' => '벽면형',
|
|
|
|
|
|
'MP' => '220V',
|
|
|
|
|
|
'CT' => '단독',
|
|
|
|
|
|
'QTY' => 1,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$service = app(\App\Services\Quote\FormulaEvaluatorService::class);
|
|
|
|
|
|
$result = $service->executeAll($input);
|
|
|
|
|
|
|
|
|
|
|
|
// Design 결과와 비교
|
|
|
|
|
|
dump([
|
|
|
|
|
|
'W1' => $result['calculated_variables']['W1'], // 예상: 2140
|
|
|
|
|
|
'H1' => $result['calculated_variables']['H1'], // 예상: 2850
|
|
|
|
|
|
'M' => $result['calculated_variables']['M'], // 예상: 6.099
|
|
|
|
|
|
'total' => $result['summary']['total_amount'], // Design과 동일해야 함
|
|
|
|
|
|
]);
|
|
|
|
|
|
```
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 핵심 파일 참조
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
### Design (참조용 - 수정 금지)
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
2025-12-24 16:11:44 +09:00
|
|
|
|
/SAM/design/src/
|
|
|
|
|
|
├── components/
|
|
|
|
|
|
│ ├── AutoCalculationSimulator.tsx # 메인 시뮬레이터 (1068줄)
|
|
|
|
|
|
│ ├── BomCalculationResults.tsx # 결과 표시 컴포넌트
|
|
|
|
|
|
│ ├── contexts/
|
|
|
|
|
|
│ │ └── DataContext.tsx # 마스터 데이터 (9859줄)
|
|
|
|
|
|
│ └── utils/
|
|
|
|
|
|
│ ├── formulaEvaluator.ts # 수식 평가 (312줄)
|
|
|
|
|
|
│ └── bomCalculatorWithDebug.ts # BOM 계산 (232줄)
|
|
|
|
|
|
└── utils/
|
|
|
|
|
|
├── sampleQuoteData_Complete.ts # 샘플 품목 데이터
|
|
|
|
|
|
└── addProductBoms.ts # BOM 구성 데이터
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### MNG (수정 대상)
|
|
|
|
|
|
```
|
|
|
|
|
|
/SAM/mng/
|
|
|
|
|
|
├── app/Services/Quote/
|
2025-12-24 16:11:44 +09:00
|
|
|
|
│ └── FormulaEvaluatorService.php # 핵심 서비스 확장 대상
|
|
|
|
|
|
├── database/
|
|
|
|
|
|
│ ├── migrations/
|
|
|
|
|
|
│ │ └── 20xx_add_simulator_fields.php # 신규 마이그레이션
|
|
|
|
|
|
│ └── seeders/
|
|
|
|
|
|
│ ├── DesignItemSeeder.php # 신규 Seeder
|
|
|
|
|
|
│ ├── DesignBomSeeder.php # 신규 Seeder
|
|
|
|
|
|
│ └── CategoryGroupSeeder.php # 신규 Seeder
|
|
|
|
|
|
├── app/Models/
|
|
|
|
|
|
│ ├── CategoryGroup.php # 신규 모델
|
|
|
|
|
|
│ ├── Item.php # 필드 추가
|
|
|
|
|
|
│ └── Price.php # 기존 모델
|
|
|
|
|
|
└── resources/views/quote-formulas/
|
|
|
|
|
|
└── simulator.blade.php # UI 확장
|
2025-12-23 23:41:47 +09:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
## 6. 작업 일정 요약
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
| Phase | 작업 내용 | 예상 일정 |
|
|
|
|
|
|
|-------|----------|----------|
|
|
|
|
|
|
| Phase 1 | DB 스키마 확장 (마이그레이션) | 1일 |
|
|
|
|
|
|
| Phase 2 | Seeder 작성 (품목/BOM/단가/CategoryGroup) | 2일 |
|
|
|
|
|
|
| Phase 3 | FormulaEvaluatorService 확장 | 2일 |
|
|
|
|
|
|
| Phase 4 | simulator.blade.php UI 개선 | 1일 |
|
|
|
|
|
|
| Phase 5 | 검증 및 동기화 테스트 | 1일 |
|
|
|
|
|
|
| **합계** | | **7일** |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 성공 기준
|
|
|
|
|
|
|
|
|
|
|
|
1. **계산 결과 동일**: Design과 MNG에서 동일 입력 시 동일한 금액 산출
|
|
|
|
|
|
2. **10단계 디버깅**: 각 품목별 계산 과정을 10단계로 확인 가능
|
|
|
|
|
|
3. **공정별 그룹화**: 스크린/절곡/전기 공정별로 품목 분류
|
|
|
|
|
|
4. **단가 우선순위**: prices 테이블 > items.salesPrice 순서 적용
|
|
|
|
|
|
5. **면적/중량 기반 단가**: CategoryGroup 설정에 따라 자동 계산
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. Serena 컨텍스트 유지 정책
|
|
|
|
|
|
|
|
|
|
|
|
> **목적**: 세션 간 컨텍스트 유지를 위해 Serena MCP 메모리에 역할별 분리 저장
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 메모리 구조
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
simulator-rules.md # 패턴, 규칙, 체크리스트
|
|
|
|
|
|
simulator-mappings.md # 필드 매핑 상세 (Design ↔ MNG)
|
|
|
|
|
|
simulator-progress.md # 진행 상황
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 8.2 메모리 내용
|
|
|
|
|
|
|
|
|
|
|
|
#### `simulator-rules.md`
|
|
|
|
|
|
- 계산 변수 체계 (W0, H0, W1, H1, M, K 등)
|
|
|
|
|
|
- 수식 평가 함수 목록 (SUM, CEIL, FLOOR, ROUND, IF 등)
|
|
|
|
|
|
- BOM 10단계 계산 프로세스
|
|
|
|
|
|
- 단가 우선순위 규칙
|
|
|
|
|
|
- 체크리스트
|
|
|
|
|
|
|
|
|
|
|
|
#### `simulator-mappings.md`
|
|
|
|
|
|
- Design TypeScript 인터페이스 ↔ MNG DB 테이블 매핑
|
|
|
|
|
|
- 품목 타입 매핑 (RM, SM, SF, FG, PT, CS)
|
|
|
|
|
|
- CategoryGroup 매핑
|
|
|
|
|
|
- 공정 타입 매핑 (screen, bending, electric, assembly)
|
|
|
|
|
|
|
|
|
|
|
|
#### `simulator-progress.md`
|
|
|
|
|
|
- Phase별 진행 상태
|
|
|
|
|
|
- 완료된 작업 목록
|
|
|
|
|
|
- 남은 작업 및 이슈
|
|
|
|
|
|
|
|
|
|
|
|
### 8.3 세션 시작/종료 패턴
|
|
|
|
|
|
|
|
|
|
|
|
**세션 시작:**
|
|
|
|
|
|
```
|
|
|
|
|
|
list_memories() → 기존 상태 확인
|
|
|
|
|
|
read_memory("simulator-progress.md") → 진행 상황 복원
|
|
|
|
|
|
read_memory("simulator-rules.md") → 규칙 컨텍스트 로드
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**세션 종료:**
|
|
|
|
|
|
```
|
|
|
|
|
|
write_memory("simulator-progress.md", 현재 진행 상황)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 8.4 초기 메모리 저장 명령
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 세션 시작 시 아래 명령으로 메모리 초기화
|
|
|
|
|
|
/sc:save simulator-rules # 규칙 저장
|
|
|
|
|
|
/sc:save simulator-mappings # 매핑 저장
|
|
|
|
|
|
/sc:save simulator-progress # 진행 상황 저장
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 검증 결과 (Phase 5)
|
|
|
|
|
|
|
|
|
|
|
|
> **검증일**: 2025-12-24
|
|
|
|
|
|
> **테스트 환경**: Docker (sam-mng-1)
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 테스트 케이스: FG-SCR-001 (W0=2000, H0=2500)
|
|
|
|
|
|
|
|
|
|
|
|
#### 변수 계산 (Design 마진 적용)
|
|
|
|
|
|
| 변수 | 계산식 | 결과 | 상태 |
|
|
|
|
|
|
|------|--------|------|------|
|
|
|
|
|
|
| W0 | 입력값 | 2000 | ✅ |
|
|
|
|
|
|
| H0 | 입력값 | 2500 | ✅ |
|
|
|
|
|
|
| W1 | W0 + 140 | 2140 | ✅ |
|
|
|
|
|
|
| H1 | H0 + 350 | 2850 | ✅ |
|
|
|
|
|
|
| M | W1 × H1 / 1,000,000 | 6.099 ㎡ | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
#### 품목별 계산 결과
|
|
|
|
|
|
| 품목코드 | 그룹 | 수량 | 단가 | 금액 | 상태 |
|
|
|
|
|
|
|----------|------|------|------|------|------|
|
|
|
|
|
|
| SF-SCR-F01 | area_based | 6.10 | 35,000 | 213,465원 | ✅ |
|
|
|
|
|
|
| SF-SCR-F02 | quantity_based | 2.85 | 42,000 | 119,700원 | ✅ |
|
|
|
|
|
|
| SF-SCR-F03 | quantity_based | 2.85 | 42,000 | 119,700원 | ✅ |
|
|
|
|
|
|
| SF-SCR-F04 | quantity_based | 1.00 | 145,000 | 145,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-F05 | (미등록) | 1.00 | 55,000 | 55,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-M01 | quantity_based | 1.00 | 350,000 | 350,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-C01 | quantity_based | 1.00 | 280,000 | 280,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-S01 | (미등록) | 1.00 | 180,000 | 180,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-W01 | (미등록) | 1.00 | 125,000 | 125,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-B01 | quantity_based | 1.00 | 78,000 | 78,000원 | ✅ |
|
|
|
|
|
|
| SF-SCR-SW01 | quantity_based | 1.00 | 45,000 | 45,000원 | ✅ |
|
|
|
|
|
|
| SM-B002 | quantity_based | 1.00 | 200 | 200원 | ✅ |
|
|
|
|
|
|
| SM-N002 | quantity_based | 1.00 | 100 | 100원 | ✅ |
|
|
|
|
|
|
| SM-W002 | quantity_based | 1.00 | 60 | 60원 | ✅ |
|
|
|
|
|
|
| **합계** | | | | **1,711,225원** | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 10단계 디버깅 검증
|
|
|
|
|
|
|
|
|
|
|
|
| 단계 | 항목 | 상태 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| Step 1 | 입력값수집 | ✅ |
|
|
|
|
|
|
| Step 2 | 변수계산 | ✅ |
|
|
|
|
|
|
| Step 3 | 완제품선택 | ✅ |
|
|
|
|
|
|
| Step 4 | BOM전개 | ✅ |
|
|
|
|
|
|
| Step 5 | 단가출처 | ✅ |
|
|
|
|
|
|
| Step 6 | 수량계산 | ✅ |
|
|
|
|
|
|
| Step 7 | 금액계산 | ✅ |
|
|
|
|
|
|
| Step 8 | 공정그룹화 | ✅ |
|
|
|
|
|
|
| Step 9 | 소계계산 | ✅ |
|
|
|
|
|
|
| Step 10 | 최종합계 | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 공정별 그룹화 검증
|
|
|
|
|
|
|
|
|
|
|
|
| 공정 | 품목 수 | 소계 | 상태 |
|
|
|
|
|
|
|------|---------|------|------|
|
|
|
|
|
|
| screen | 11 | 1,710,865원 | ✅ |
|
|
|
|
|
|
| assembly | 3 | 360원 | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.4 단가 우선순위 검증
|
|
|
|
|
|
|
|
|
|
|
|
| 품목 | 단가 출처 | 상태 |
|
|
|
|
|
|
|------|----------|------|
|
|
|
|
|
|
| SF-SCR-F01 | items.salesPrice | ✅ |
|
|
|
|
|
|
| SF-SCR-M01 | items.salesPrice | ✅ |
|
|
|
|
|
|
| SM-B002 | items.salesPrice | ✅ |
|
|
|
|
|
|
|
2025-12-29 21:43:28 +09:00
|
|
|
|
> **참고**: ~~prices 테이블에 active 데이터 없음~~ → **2025-12-29 prices 데이터 85개 추가 완료**
|
2025-12-24 16:11:44 +09:00
|
|
|
|
|
|
|
|
|
|
### 9.5 성공 기준 달성 현황
|
|
|
|
|
|
|
|
|
|
|
|
| 기준 | 달성 | 비고 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 계산 결과 동일 | ✅ | Design 마진 (W+140, H+350) 적용 |
|
|
|
|
|
|
| 10단계 디버깅 | ✅ | 모든 단계 정상 출력 |
|
|
|
|
|
|
| 공정별 그룹화 | ✅ | screen, assembly 분류 |
|
|
|
|
|
|
| 단가 우선순위 | ✅ | prices → items.salesPrice 순서 |
|
|
|
|
|
|
| 면적/중량 기반 단가 | ✅ | CategoryGroup 기반 자동 계산 |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.6 수정 사항 (Phase 5 중)
|
|
|
|
|
|
|
|
|
|
|
|
1. **면적기반 단가 중복 계산 수정**
|
|
|
|
|
|
- 문제: `total = quantity × (base_price × multiplier)` (중복)
|
|
|
|
|
|
- 수정: 면적/중량기반은 `total = final_price` (이미 multiplier 적용됨)
|
|
|
|
|
|
|
|
|
|
|
|
2. **마진값 Design 표준 적용**
|
|
|
|
|
|
- 기존: W+100, H+100
|
|
|
|
|
|
- 수정: W+140, H+350 (스크린 기준)
|
2025-12-23 23:41:47 +09:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-29 21:43:28 +09:00
|
|
|
|
## 10. Phase 6: prices 테이블 데이터 추가 (2025-12-29)
|
|
|
|
|
|
|
|
|
|
|
|
### 10.1 작업 내용
|
|
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| 작업일 | 2025-12-29 |
|
|
|
|
|
|
| 목적 | prices 테이블에 시뮬레이터용 단가 데이터 추가 |
|
|
|
|
|
|
| Seeder | `DesignPriceSeeder.php` |
|
|
|
|
|
|
| 대상 품목 | 85개 (RM, SM, SF-SCR, SF-STL, SF-BND) |
|
|
|
|
|
|
|
|
|
|
|
|
### 10.2 생성된 Seeder
|
|
|
|
|
|
|
|
|
|
|
|
**파일**: `mng/database/seeders/DesignPriceSeeder.php`
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
|
// items.attributes.salesPrice → prices 테이블 이전
|
|
|
|
|
|
// 단가 우선순위: prices (1순위) → items.attributes (2순위)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**실행 명령**:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
php artisan db:seed --class=DesignPriceSeeder
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 10.3 추가된 데이터
|
|
|
|
|
|
|
|
|
|
|
|
| 품목 유형 | 코드 패턴 | 수량 |
|
|
|
|
|
|
|----------|----------|------|
|
|
|
|
|
|
| 원자재 | RM-* | 20개 |
|
|
|
|
|
|
| 부자재 | SM-* | 25개 |
|
|
|
|
|
|
| 스크린 반제품 | SF-SCR-* | 20개 |
|
|
|
|
|
|
| 철재 반제품 | SF-STL-* | 16개 |
|
|
|
|
|
|
| 절곡 반제품 | SF-BND-* | 4개 |
|
|
|
|
|
|
| **합계** | | **85개** |
|
|
|
|
|
|
|
|
|
|
|
|
### 10.4 단가 우선순위 검증 결과
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
=== prices 우선순위 테스트 ===
|
|
|
|
|
|
prices 테이블: 99,999원 (테스트용 변경)
|
|
|
|
|
|
items.attributes: 35,000원 (그대로)
|
|
|
|
|
|
getSalesPriceByItemCode(): 99,999원
|
|
|
|
|
|
|
|
|
|
|
|
✓ prices 테이블 우선 적용 확인!
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 10.5 FormulaEvaluatorService 단가 조회 로직
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
|
// mng/app/Services/Quote/FormulaEvaluatorService.php:379-410
|
|
|
|
|
|
private function getItemPrice(string $itemCode): float
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1순위: Price 모델에서 조회
|
|
|
|
|
|
$price = Price::getSalesPriceByItemCode($tenantId, $itemCode);
|
|
|
|
|
|
if ($price > 0) {
|
|
|
|
|
|
return $price;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2순위: Fallback - items.attributes.salesPrice
|
|
|
|
|
|
$item = DB::table('items')->where('code', $itemCode)->first();
|
|
|
|
|
|
return (float) ($attributes['salesPrice'] ?? 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-30 14:46:26 +09:00
|
|
|
|
## 11. Phase 7: 철재 제품 테스트 케이스 (2025-12-30)
|
|
|
|
|
|
|
|
|
|
|
|
### 11.1 작업 개요
|
|
|
|
|
|
|
|
|
|
|
|
| 항목 | 내용 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| 작업일 | 2025-12-30 |
|
|
|
|
|
|
| 목적 | 철재 제품(FG-STL-*) 마진값/중량 계산 동기화 및 CategoryGroup 적용 |
|
|
|
|
|
|
| 테스트 완제품 | FG-STL-001 (철재 방화문) |
|
|
|
|
|
|
| 입력값 | W0=2000, H0=2500 |
|
|
|
|
|
|
|
|
|
|
|
|
### 11.2 수정 사항
|
|
|
|
|
|
|
|
|
|
|
|
#### 11.2.1 마진값 동적 적용 (SCREEN/STEEL 분기)
|
|
|
|
|
|
|
|
|
|
|
|
**파일**: `mng/app/Services/Quote/FormulaEvaluatorService.php`
|
|
|
|
|
|
|
|
|
|
|
|
| 제품 카테고리 | 마진 W | 마진 H | K 계산식 |
|
|
|
|
|
|
|-------------|-------|-------|---------|
|
|
|
|
|
|
| SCREEN (스크린) | W0+140 | H0+350 | M×2 + W0/1000×14.17 |
|
|
|
|
|
|
| STEEL (철재) | W0+110 | H0+350 | M×25 |
|
|
|
|
|
|
|
|
|
|
|
|
**변경 내용**:
|
|
|
|
|
|
```php
|
|
|
|
|
|
// 제품 카테고리에 따른 마진값 결정
|
|
|
|
|
|
if (strtoupper($productCategory) === 'STEEL') {
|
|
|
|
|
|
$marginW = 110; // 철재 마진
|
|
|
|
|
|
$K = $M * 25; // 철재 중량
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$marginW = 140; // 스크린 기본 마진
|
|
|
|
|
|
$K = $M * 2 + ($W0 / 1000) * 14.17; // 스크린 중량
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 11.2.2 CategoryGroup 데이터 생성 (tenant 287)
|
|
|
|
|
|
|
|
|
|
|
|
**문제**: CategoryGroup 데이터가 tenant_id=1에만 존재, tenant_id=287 미등록
|
|
|
|
|
|
|
|
|
|
|
|
**해결**: tenant 287용 CategoryGroup 3종 생성
|
|
|
|
|
|
|
|
|
|
|
|
| 코드 | 이름 | 승수변수 | 포함 카테고리 |
|
|
|
|
|
|
|------|------|---------|-------------|
|
|
|
|
|
|
| area_based | 면적기반 | M | 원단, 패널, 도장, 표면처리, 유리, 도어, 프레임, 창틀 |
|
|
|
|
|
|
| weight_based | 중량기반 | K | 강판, 알루미늄, 스테인리스, 철재 |
|
|
|
|
|
|
| quantity_based | 수량기반 | (없음) | 볼트, 경첩, 도어락, 도어클로저, 실링재, 문턱, 킥플레이트 등 |
|
|
|
|
|
|
|
|
|
|
|
|
### 11.3 테스트 결과
|
|
|
|
|
|
|
|
|
|
|
|
#### 11.3.1 변수 계산 검증
|
|
|
|
|
|
|
|
|
|
|
|
| 변수 | 계산값 | 예상값 | 상태 |
|
|
|
|
|
|
|------|-------|-------|------|
|
|
|
|
|
|
| W1 | 2110 | 2110 (W0+110) | ✅ |
|
|
|
|
|
|
| H1 | 2850 | 2850 (H0+350) | ✅ |
|
|
|
|
|
|
| M | 6.0135 ㎡ | 6.0135 | ✅ |
|
|
|
|
|
|
| K | 150.34 kg | 150.34 (M×25) | ✅ |
|
|
|
|
|
|
| PC | STEEL | STEEL | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
#### 11.3.2 CategoryGroup 적용 검증
|
|
|
|
|
|
|
|
|
|
|
|
| 품목 | 카테고리 | CategoryGroup | 기준단가 | 승수 | 최종단가 |
|
|
|
|
|
|
|------|---------|--------------|---------|------|---------|
|
|
|
|
|
|
| 철재 도어 | 도어 | area_based | 320,000 | M×6.01 | 1,924,320원 |
|
|
|
|
|
|
| 철재 프레임 | 프레임 | area_based | 58,000 | M×6.01 | 348,783원 |
|
|
|
|
|
|
| 철재 패널 | 패널 | area_based | 68,000 | M×6.01 | 408,918원 |
|
|
|
|
|
|
| 경첩 세트 | 경첩 | quantity_based | 42,000 | - | 42,000원 |
|
|
|
|
|
|
| 도어락 | 도어락 | quantity_based | 95,000 | - | 95,000원 |
|
|
|
|
|
|
| 도어클로저 | 도어클로저 | quantity_based | 115,000 | - | 115,000원 |
|
|
|
|
|
|
| 실링재 | 실링재 | quantity_based | 9,500 | - | 9,500원 |
|
|
|
|
|
|
| 문턱 | 문턱 | quantity_based | 58,000 | - | 58,000원 |
|
|
|
|
|
|
| 킥플레이트 | 킥플레이트 | quantity_based | 45,000 | - | 45,000원 |
|
|
|
|
|
|
| 볼트 세트 | 볼트 | quantity_based | 18,000 | - | 18,000원 |
|
|
|
|
|
|
|
|
|
|
|
|
**최종 합계**: 3,158,111원 ✅
|
|
|
|
|
|
|
|
|
|
|
|
### 11.4 수정된 파일
|
|
|
|
|
|
|
|
|
|
|
|
| 파일 | 수정 내용 |
|
|
|
|
|
|
|------|----------|
|
|
|
|
|
|
| `FormulaEvaluatorService.php` | 마진값/K계산 동적 분기, `getItemDetails()`에 item_category 추가 |
|
|
|
|
|
|
| `category_groups` (DB) | tenant 287용 3개 그룹 생성 |
|
|
|
|
|
|
|
|
|
|
|
|
### 11.5 성공 기준 달성
|
|
|
|
|
|
|
|
|
|
|
|
| 기준 | 달성 | 비고 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 철재 마진 적용 | ✅ | W+110 정상 적용 |
|
|
|
|
|
|
| 철재 중량 계산 | ✅ | M×25 정상 적용 |
|
|
|
|
|
|
| CategoryGroup 매칭 | ✅ | area_based, quantity_based 정상 |
|
|
|
|
|
|
| 면적기반 단가 계산 | ✅ | base_price × M 정상 |
|
|
|
|
|
|
| 수량기반 단가 계산 | ✅ | base_price 그대로 적용 |
|
|
|
|
|
|
|
|
|
|
|
|
### 11.6 절곡 제품 테스트 (FG-BND-001)
|
|
|
|
|
|
|
|
|
|
|
|
#### 테스트 결과
|
|
|
|
|
|
|
|
|
|
|
|
| 변수 | 계산값 | 상태 |
|
|
|
|
|
|
|------|-------|------|
|
|
|
|
|
|
| W1 | 2110 (W0+110) | ✅ 철재 마진 적용 |
|
|
|
|
|
|
| M | 6.0135 ㎡ | ✅ |
|
|
|
|
|
|
| K | 150.34 kg (M×25) | ✅ 철재 중량 |
|
|
|
|
|
|
| PC | STEEL | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
#### CategoryGroup 수정
|
|
|
|
|
|
|
|
|
|
|
|
**문제**: "절곡" 카테고리가 CategoryGroup 미등록 → 단가 0원
|
|
|
|
|
|
|
|
|
|
|
|
**해결**: `area_based`에 "절곡" 카테고리 추가
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
// area_based categories (수정 후)
|
|
|
|
|
|
["원단","패널","도장","표면처리","스크린원단","유리","도어","프레임","창틀","절곡"]
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 수정 후 단가 계산
|
|
|
|
|
|
|
|
|
|
|
|
| 품목 | CategoryGroup | 기준단가 | 승수 | 최종단가 |
|
|
|
|
|
|
|------|--------------|---------|------|---------|
|
|
|
|
|
|
| 절곡 | area_based | 28,000 | M×6.01 | 168,378원 |
|
|
|
|
|
|
| 프레임 | area_based | 58,000 | M×6.01 | 348,783원 |
|
|
|
|
|
|
| 도장 | area_based | 32,000 | M×6.01 | 192,432원 |
|
|
|
|
|
|
| 볼트 | quantity_based | 18,000 | - | 18,000원 |
|
|
|
|
|
|
|
|
|
|
|
|
**최종 합계**: 727,893원 ✅
|
|
|
|
|
|
|
|
|
|
|
|
### 11.7 전체 제품 유형 검증 완료
|
|
|
|
|
|
|
|
|
|
|
|
| 제품 유형 | 코드 | 마진 | K 계산 | 합계 |
|
|
|
|
|
|
|----------|------|------|--------|------|
|
|
|
|
|
|
| 스크린 | FG-SCR-001 | W+140 ✅ | M×2+W0/1000×14.17 ✅ | 1,711,225원 |
|
|
|
|
|
|
| 철재 | FG-STL-001 | W+110 ✅ | M×25 ✅ | 3,158,111원 |
|
|
|
|
|
|
| 절곡 | FG-BND-001 | W+110 ✅ | M×25 ✅ | 727,893원 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-12-24 16:11:44 +09:00
|
|
|
|
*이 문서는 design.sam.kr 완전 분석을 바탕으로 mng 시뮬레이터 완전 동기화 계획을 상세히 기술합니다.*
|