feat: 경동기업 전용 견적 계산 로직 구현 (Phase 4 완료)
- KdPriceTable 모델: 경동기업 단가 테이블 (motor, shaft, pipe, angle, raw_material, bdmodels) - KyungdongFormulaHandler: 모터 용량, 브라켓 크기, 절곡품(10종), 부자재(3종) 계산 - FormulaEvaluatorService: tenant_id=287 라우팅 추가 - kd_price_tables 마이그레이션 및 시더 (47건 단가 데이터) 테스트 결과: W0=3000, H0=2500 입력 시 16개 항목, 합계 751,200원 정상 계산 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
use App\Models\Items\Item;
|
||||
use App\Models\Products\Price;
|
||||
use App\Models\Quote\QuoteFormula;
|
||||
use App\Services\Quote\Handlers\KyungdongFormulaHandler;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -28,6 +29,11 @@ class FormulaEvaluatorService extends Service
|
||||
'SUM', 'ROUND', 'CEIL', 'FLOOR', 'ABS', 'MIN', 'MAX', 'IF', 'AND', 'OR', 'NOT',
|
||||
];
|
||||
|
||||
/**
|
||||
* 경동기업 테넌트 ID
|
||||
*/
|
||||
private const KYUNGDONG_TENANT_ID = 287;
|
||||
|
||||
private array $variables = [];
|
||||
|
||||
private array $errors = [];
|
||||
@@ -599,6 +605,11 @@ public function calculateBomWithDebug(
|
||||
];
|
||||
}
|
||||
|
||||
// 경동기업(tenant_id=287) 전용 계산 로직 분기
|
||||
if ($tenantId === self::KYUNGDONG_TENANT_ID) {
|
||||
return $this->calculateKyungdongBom($finishedGoodsCode, $inputVariables, $tenantId);
|
||||
}
|
||||
|
||||
// Step 1: 입력값 수집 (React 동기화 변수 포함)
|
||||
$this->addDebugStep(1, '입력값수집', [
|
||||
'W0' => $inputVariables['W0'] ?? null,
|
||||
@@ -1538,4 +1549,219 @@ private function mergeLeafMaterials(array $leafMaterials, int $tenantId): array
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 경동기업 전용 계산 (tenant_id = 287)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 경동기업 전용 BOM 계산
|
||||
*
|
||||
* 5130 레거시 시스템의 견적 로직을 구현한 KyungdongFormulaHandler 사용
|
||||
* - 3차원 조건 모터 용량 계산 (제품타입 × 인치 × 중량)
|
||||
* - 브라켓 크기 결정
|
||||
* - 10종 절곡품 계산
|
||||
* - 3종 부자재 계산
|
||||
*
|
||||
* @param string $finishedGoodsCode 완제품 코드
|
||||
* @param array $inputVariables 입력 변수 (W0, H0, QTY 등)
|
||||
* @param int $tenantId 테넌트 ID
|
||||
* @return array 계산 결과
|
||||
*/
|
||||
private function calculateKyungdongBom(
|
||||
string $finishedGoodsCode,
|
||||
array $inputVariables,
|
||||
int $tenantId
|
||||
): array {
|
||||
$this->addDebugStep(0, '경동전용계산', [
|
||||
'tenant_id' => $tenantId,
|
||||
'handler' => 'KyungdongFormulaHandler',
|
||||
'finished_goods' => $finishedGoodsCode,
|
||||
]);
|
||||
|
||||
// Step 1: 입력값 수집
|
||||
$this->addDebugStep(1, '입력값수집', [
|
||||
'W0' => $inputVariables['W0'] ?? null,
|
||||
'H0' => $inputVariables['H0'] ?? null,
|
||||
'QTY' => $inputVariables['QTY'] ?? 1,
|
||||
'bracket_inch' => $inputVariables['bracket_inch'] ?? '5',
|
||||
'product_type' => $inputVariables['product_type'] ?? 'screen',
|
||||
'finishing_type' => $inputVariables['finishing_type'] ?? 'SUS',
|
||||
'finished_goods' => $finishedGoodsCode,
|
||||
]);
|
||||
|
||||
// Step 2: 완제품 조회
|
||||
$finishedGoods = $this->getItemDetails($finishedGoodsCode, $tenantId);
|
||||
|
||||
if (! $finishedGoods) {
|
||||
$this->addDebugStep(2, '완제품선택', [
|
||||
'code' => $finishedGoodsCode,
|
||||
'error' => '완제품을 찾을 수 없습니다.',
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('error.finished_goods_not_found', ['code' => $finishedGoodsCode]),
|
||||
'debug_steps' => $this->debugSteps,
|
||||
];
|
||||
}
|
||||
|
||||
$this->addDebugStep(2, '완제품선택', [
|
||||
'code' => $finishedGoods['code'],
|
||||
'name' => $finishedGoods['name'],
|
||||
'item_category' => $finishedGoods['item_category'] ?? 'N/A',
|
||||
]);
|
||||
|
||||
// KyungdongFormulaHandler 인스턴스 생성
|
||||
$handler = new KyungdongFormulaHandler;
|
||||
|
||||
// Step 3: 경동 전용 변수 계산
|
||||
$W0 = (float) ($inputVariables['W0'] ?? 0);
|
||||
$H0 = (float) ($inputVariables['H0'] ?? 0);
|
||||
$QTY = (int) ($inputVariables['QTY'] ?? 1);
|
||||
$bracketInch = $inputVariables['bracket_inch'] ?? '5';
|
||||
$productType = $inputVariables['product_type'] ?? 'screen';
|
||||
|
||||
// 중량 계산 (5130 로직)
|
||||
$area = ($W0 * ($H0 + 550)) / 1000000;
|
||||
$weight = $area * ($productType === 'steel' ? 25 : 2) + ($W0 / 1000) * 14.17;
|
||||
|
||||
// 모터 용량 결정
|
||||
$motorCapacity = $handler->calculateMotorCapacity($productType, $weight, $bracketInch);
|
||||
|
||||
// 브라켓 크기 결정
|
||||
$bracketSize = $handler->calculateBracketSize($weight, $bracketInch);
|
||||
|
||||
$calculatedVariables = array_merge($inputVariables, [
|
||||
'W0' => $W0,
|
||||
'H0' => $H0,
|
||||
'QTY' => $QTY,
|
||||
'W1' => $W0 + 140,
|
||||
'H1' => $H0 + 350,
|
||||
'AREA' => round($area, 4),
|
||||
'WEIGHT' => round($weight, 2),
|
||||
'MOTOR_CAPACITY' => $motorCapacity,
|
||||
'BRACKET_SIZE' => $bracketSize,
|
||||
'bracket_inch' => $bracketInch,
|
||||
'product_type' => $productType,
|
||||
]);
|
||||
|
||||
$this->addDebugStep(3, '변수계산', [
|
||||
'W0' => $W0,
|
||||
'H0' => $H0,
|
||||
'area' => round($area, 4),
|
||||
'weight' => round($weight, 2),
|
||||
'motor_capacity' => $motorCapacity,
|
||||
'bracket_size' => $bracketSize,
|
||||
'calculation_type' => '경동기업 전용 공식',
|
||||
]);
|
||||
|
||||
// Step 4-7: 동적 항목 계산 (KyungdongFormulaHandler 사용)
|
||||
$dynamicItems = $handler->calculateDynamicItems($calculatedVariables);
|
||||
|
||||
$this->addDebugStep(4, 'BOM전개', [
|
||||
'total_items' => count($dynamicItems),
|
||||
'item_categories' => array_unique(array_column($dynamicItems, 'category')),
|
||||
]);
|
||||
|
||||
// Step 5-7: 단가 계산 (각 항목별)
|
||||
$calculatedItems = [];
|
||||
foreach ($dynamicItems as $item) {
|
||||
$this->addDebugStep(6, '수량계산', [
|
||||
'item_name' => $item['item_name'],
|
||||
'quantity' => $item['quantity'],
|
||||
]);
|
||||
|
||||
$this->addDebugStep(7, '금액계산', [
|
||||
'item_name' => $item['item_name'],
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_price' => $item['unit_price'],
|
||||
'total_price' => $item['total_price'],
|
||||
]);
|
||||
|
||||
$calculatedItems[] = [
|
||||
'item_code' => $item['item_code'] ?? '',
|
||||
'item_name' => $item['item_name'],
|
||||
'item_category' => $item['category'],
|
||||
'specification' => $item['specification'] ?? '',
|
||||
'unit' => $item['unit'],
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_price' => $item['unit_price'],
|
||||
'total_price' => $item['total_price'],
|
||||
'category_group' => $item['category'],
|
||||
'calculation_note' => '경동기업 전용 계산',
|
||||
];
|
||||
}
|
||||
|
||||
// Step 8: 카테고리별 그룹화
|
||||
$groupedItems = [];
|
||||
foreach ($calculatedItems as $item) {
|
||||
$category = $item['category_group'];
|
||||
if (! isset($groupedItems[$category])) {
|
||||
$groupedItems[$category] = [
|
||||
'name' => $this->getKyungdongCategoryName($category),
|
||||
'items' => [],
|
||||
'subtotal' => 0,
|
||||
];
|
||||
}
|
||||
$groupedItems[$category]['items'][] = $item;
|
||||
$groupedItems[$category]['subtotal'] += $item['total_price'];
|
||||
}
|
||||
|
||||
$this->addDebugStep(8, '카테고리그룹화', [
|
||||
'groups' => array_map(fn ($g) => [
|
||||
'name' => $g['name'],
|
||||
'count' => count($g['items']),
|
||||
'subtotal' => $g['subtotal'],
|
||||
], $groupedItems),
|
||||
]);
|
||||
|
||||
// Step 9: 소계 계산
|
||||
$subtotals = [];
|
||||
foreach ($groupedItems as $category => $group) {
|
||||
$subtotals[$category] = [
|
||||
'name' => $group['name'],
|
||||
'count' => count($group['items']),
|
||||
'subtotal' => $group['subtotal'],
|
||||
];
|
||||
}
|
||||
|
||||
$this->addDebugStep(9, '소계계산', $subtotals);
|
||||
|
||||
// Step 10: 최종 합계
|
||||
$grandTotal = array_sum(array_column($calculatedItems, 'total_price'));
|
||||
|
||||
$this->addDebugStep(10, '최종합계', [
|
||||
'item_count' => count($calculatedItems),
|
||||
'grand_total' => $grandTotal,
|
||||
'formatted' => number_format($grandTotal).'원',
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'finished_goods' => $finishedGoods,
|
||||
'variables' => $calculatedVariables,
|
||||
'items' => $calculatedItems,
|
||||
'grouped_items' => $groupedItems,
|
||||
'subtotals' => $subtotals,
|
||||
'grand_total' => $grandTotal,
|
||||
'debug_steps' => $this->debugSteps,
|
||||
'calculation_type' => 'kyungdong',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 경동기업 카테고리명 반환
|
||||
*/
|
||||
private function getKyungdongCategoryName(string $category): string
|
||||
{
|
||||
return match ($category) {
|
||||
'material' => '주자재',
|
||||
'motor' => '모터',
|
||||
'controller' => '제어기',
|
||||
'steel' => '절곡품',
|
||||
'parts' => '부자재',
|
||||
default => $category,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user