Files
sam-docs/plans/archive/simulator-calculation-logic-mapping.md
권혁성 28b69e5449 docs: archive 37개 + COMPLETED 3개 복원 - 향후 docs/ 정식 문서화 시 참조용
- 완료 문서의 상세 내용은 추후 docs/ 구조화 시 정식 문서에 반영 예정
- HISTORY.md는 요약 인덱스로 유지, 개별 파일은 상세 참조용 보관

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:32:20 +09:00

35 KiB
Raw Blame History

견적 시뮬레이터 완전 동기화 계획

작성일: 2025-12-23 (업데이트: 2025-12-30) 목표: design.sam.kr 시뮬레이터와 mng 시뮬레이터가 동일한 결과를 출력하도록 완전 동기화


1. Design 시스템 전체 분석

1.1 핵심 파일 구조

파일 줄 수 역할
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)

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)

interface BOMLine {
  childItemCode: string;      // 자식 품목 코드
  childItemName: string;      // 자식 품목명
  quantity: number;           // 기준 수량
  unit: string;               // 단위
  quantityFormula?: string;   // 수량 수식 (예: "W*H/1000000", "H/1000")
  note?: string;              // 비고
}

단가 관리 (PricingData)

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';
}

카테고리 그룹 (CategoryGroup) - MNG에 누락

interface CategoryGroup {
  id: string;
  name: string;               // "면적기반", "중량기반", "수량기반"
  categories: string[];       // 소속 카테고리들
  multiplierVariable?: string; // 곱할 변수 (M, K 등)
}

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 수식 평가 함수

지원 함수 목록:

함수 설명 예시
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(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)
POWER(base, exp) 거듭제곱 POWER(W1, 2)

평가 과정:

// 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]));
});

// 2. 함수 처리 (CEIL, FLOOR, ROUND 등)
formula = processFunctions(formula);

// 3. 최종 계산
return new Function(`return (${formula})`)();

1.5 BOM 계산 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원) 또는 품목마스터 (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.6 단가 계산 로직

// 1. 단가 조회 우선순위
let unitPrice = 0;
let priceSource = '단가 없음';

// 1순위: pricing 테이블에서 조회
const itemPricing = pricings.find(p => p.itemCode === bomEntry.childItemCode);
if (itemPricing && itemPricing.salesPrice) {
  unitPrice = itemPricing.salesPrice;
  priceSource = `단가관리 (${unitPrice.toLocaleString()}원)`;
}
// 2순위: 품목마스터에서 조회
else if (childItem.salesPrice) {
  unitPrice = childItem.salesPrice;
  priceSource = `품목마스터 (${unitPrice.toLocaleString()}원)`;
}

// 2. 면적 기반 품목 판단
const areaBasedCategories = ['원단', '패널', '도장', '표면처리'];
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 = '수량단가';
}

// 4. 최종 금액
const totalPrice = calculatedQuantity * finalUnitPrice;

2. Design 샘플 데이터 분석

2.1 품목 구성 (약 100개)

유형 코드 접두사 수량 설명
원자재 (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형/ㄷ형

2.2 주요 BOM 수식 패턴

품목 유형 수식 설명
스크린 원단 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 면적 계산

2.3 완제품 BOM 예시 (FG-SCR-002 중형 스크린)

{
  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 추가 필요

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 중간

4. 완전 동기화 구현 계획

Phase 1: DB 스키마 확장 (1일)

1.1 items 테이블 필드 추가

ALTER TABLE items ADD COLUMN process_type VARCHAR(20) DEFAULT NULL
  COMMENT '공정유형: screen(스크린), bending(절곡), electric(전기), steel(철재)';

ALTER TABLE items ADD COLUMN item_category VARCHAR(50) DEFAULT NULL
  COMMENT '품목분류: 원단, 패널, 도장, 표면처리, 가이드레일, 케이스, 모터, 제어반 등';

CREATE INDEX idx_items_process_type ON items(process_type);
CREATE INDEX idx_items_item_category ON items(item_category);

1.2 category_groups 테이블 생성

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

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개 더
        ];
    }
}

2.2 BOM 구성 Seeder

파일: database/seeders/DesignBomSeeder.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

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(['볼트', '너트', '모터', '제어반']),
            ],
        ];
    }
}

Phase 3: 백엔드 로직 확장 (2일)

3.1 FormulaEvaluatorService 확장

추가할 메서드:

/**
 * 카테고리 기반 단가 계산
 */
private function calculateCategoryPrice(
    array $item,
    float $basePrice,
    array $variables
): array {
    $categoryGroup = CategoryGroup::query()
        ->whereJsonContains('categories', $item['item_category'] ?? '')
        ->first();

    if (!$categoryGroup || !$categoryGroup->multiplier_variable) {
        return [
            'final_price' => $basePrice,
            'calculation_note' => '수량단가',
            'multiplier' => 1,
        ];
    }

    $multiplierVar = $categoryGroup->multiplier_variable;
    $multiplierValue = $variables[$multiplierVar] ?? 1;

    return [
        'final_price' => $basePrice * $multiplierValue,
        'calculation_note' => "{$categoryGroup->name} ({$basePrice}원/{$this->getUnit($multiplierVar)} × {$multiplierValue})",
        'multiplier' => $multiplierValue,
    ];
}

/**
 * 공정별 품목 그룹화
 */
private function groupItemsByProcess(array $items): array
{
    $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],
    ];

    foreach ($items as $item) {
        $process = $item['process_type'] ?? 'etc';
        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;
        }
    }

    return array_filter($processOrder, fn($g) => count($g['items']) > 0);
}

/**
 * 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,
    ];
}

3.2 executeAll() 반환 구조 확장

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;
    }

    // 4. 공정별 그룹화
    $groupedByProcess = $this->groupItemsByProcess($bomItems);

    // 5. 총합계
    $totalAmount = array_sum(array_column($bomItems, 'total_price'));

    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 결과 표시 개선

{{-- 공정별 그룹화 결과 --}}
@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>

Phase 5: 검증 및 동기화 (1일)

5.1 테스트 케이스

입력값 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원 동일

5.2 검증 스크립트

// 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과 동일해야 함
]);

5. 핵심 파일 참조

Design (참조용 - 수정 금지)

/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 구성 데이터

MNG (수정 대상)

/SAM/mng/
├── app/Services/Quote/
│   └── 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 확장

6. 작업 일정 요약

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 초기 메모리 저장 명령

# 세션 시작 시 아래 명령으로 메모리 초기화
/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

참고: prices 테이블에 active 데이터 없음2025-12-29 prices 데이터 85개 추가 완료

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 (스크린 기준)

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

// items.attributes.salesPrice → prices 테이블 이전
// 단가 우선순위: prices (1순위) → items.attributes (2순위)

실행 명령:

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 단가 조회 로직

// 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);
}

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

변경 내용:

// 제품 카테고리에 따른 마진값 결정
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에 "절곡" 카테고리 추가

// 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원

이 문서는 design.sam.kr 완전 분석을 바탕으로 mng 시뮬레이터 완전 동기화 계획을 상세히 기술합니다.