fix:FG 수식 산출 시 제품모델/설치타입/마감타입 올바르게 적용
- parseFgCode() 추가: FG 코드에서 모델/설치타입/마감타입 파싱 - calculateTenantBom() 폴백 순서: 입력값 > FG코드 파싱 > 기본값(KSS01/벽면형/SUS) - KQTS01 제품이 KSS01 가이드레일 규격(120*70)으로 잘못 산출되던 문제 해결 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
40
app/Services/Quote/Contracts/TenantFormulaHandler.php
Normal file
40
app/Services/Quote/Contracts/TenantFormulaHandler.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Quote\Contracts;
|
||||
|
||||
/**
|
||||
* 테넌트별 수식 핸들러 인터페이스
|
||||
*
|
||||
* FormulaEvaluatorService에서 테넌트 전용 계산 로직을 위임받는 핸들러 계약.
|
||||
* 각 테넌트 핸들러는 Handlers/Tenant{id}/FormulaHandler.php에 위치하며,
|
||||
* FormulaHandlerFactory가 class_exists()로 자동 발견한다.
|
||||
*/
|
||||
interface TenantFormulaHandler
|
||||
{
|
||||
/**
|
||||
* 모터 용량 결정
|
||||
*
|
||||
* @param string $productType 제품 타입 (screen/steel/slat)
|
||||
* @param float $weight 중량 (kg)
|
||||
* @param string $bracketInch 브라켓 인치
|
||||
* @return string 모터 용량 코드 (예: '300K', '800K')
|
||||
*/
|
||||
public function calculateMotorCapacity(string $productType, float $weight, string $bracketInch): string;
|
||||
|
||||
/**
|
||||
* 브라켓 크기 결정
|
||||
*
|
||||
* @param float $weight 중량 (kg)
|
||||
* @param string|null $bracketInch 브라켓 인치
|
||||
* @return string 브라켓 크기 (예: '530*320', '690*390')
|
||||
*/
|
||||
public function calculateBracketSize(float $weight, ?string $bracketInch = null): string;
|
||||
|
||||
/**
|
||||
* 동적 BOM 항목 계산 (주자재, 모터, 제어기, 절곡품, 부자재)
|
||||
*
|
||||
* @param array $inputs 계산 변수 (W0, H0, QTY, product_type, product_model 등)
|
||||
* @return array BOM 항목 배열 [{item_id, item_code, item_name, category, quantity, unit_price, total_price, ...}]
|
||||
*/
|
||||
public function calculateDynamicItems(array $inputs): array;
|
||||
}
|
||||
@@ -6,7 +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\Quote\Contracts\TenantFormulaHandler;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -29,11 +29,6 @@ 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 = [];
|
||||
@@ -548,6 +543,25 @@ public function enableDebugMode(bool $enabled = true): self
|
||||
/**
|
||||
* 디버그 단계 기록
|
||||
*/
|
||||
/**
|
||||
* FG 코드에서 모델/설치타입/마감타입 파싱
|
||||
* 형식: FG-{MODEL}-{INSTALLATION}-{FINISHING} (예: FG-KQTS01-벽면형-SUS)
|
||||
* 파싱 실패 시 null 반환 (추후 FG 코드 형식 변경 대비)
|
||||
*/
|
||||
private function parseFgCode(string $code): array
|
||||
{
|
||||
$parts = explode('-', $code);
|
||||
if (count($parts) >= 4 && $parts[0] === 'FG') {
|
||||
return [
|
||||
'model' => $parts[1],
|
||||
'installation' => $parts[2],
|
||||
'finishing' => $parts[3],
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function addDebugStep(int $step, string $name, array $data): void
|
||||
{
|
||||
if (! $this->debugMode) {
|
||||
@@ -605,9 +619,10 @@ public function calculateBomWithDebug(
|
||||
];
|
||||
}
|
||||
|
||||
// 경동기업(tenant_id=287) 전용 계산 로직 분기
|
||||
if ($tenantId === self::KYUNGDONG_TENANT_ID) {
|
||||
return $this->calculateKyungdongBom($finishedGoodsCode, $inputVariables, $tenantId);
|
||||
// 테넌트 전용 핸들러 분기 (Zero Config: class_exists 자동 발견)
|
||||
$tenantHandler = FormulaHandlerFactory::make($tenantId);
|
||||
if ($tenantHandler !== null) {
|
||||
return $this->calculateTenantBom($tenantHandler, $finishedGoodsCode, $inputVariables, $tenantId);
|
||||
}
|
||||
|
||||
// Step 1: 입력값 수집 (React 동기화 변수 포함)
|
||||
@@ -1558,27 +1573,29 @@ private function mergeLeafMaterials(array $leafMaterials, int $tenantId): array
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 경동기업 전용 BOM 계산
|
||||
* 테넌트 전용 핸들러 기반 BOM 계산
|
||||
*
|
||||
* 5130 레거시 시스템의 견적 로직을 구현한 KyungdongFormulaHandler 사용
|
||||
* - 3차원 조건 모터 용량 계산 (제품타입 × 인치 × 중량)
|
||||
* - 브라켓 크기 결정
|
||||
* - 10종 절곡품 계산
|
||||
* - 3종 부자재 계산
|
||||
* FormulaHandlerFactory가 발견한 TenantFormulaHandler 구현체를 사용.
|
||||
* 핸들러가 제공하는 calculateMotorCapacity, calculateBracketSize,
|
||||
* calculateDynamicItems 메서드로 BOM을 계산한다.
|
||||
*
|
||||
* @param TenantFormulaHandler $handler 테넌트 전용 핸들러
|
||||
* @param string $finishedGoodsCode 완제품 코드
|
||||
* @param array $inputVariables 입력 변수 (W0, H0, QTY 등)
|
||||
* @param int $tenantId 테넌트 ID
|
||||
* @return array 계산 결과
|
||||
*/
|
||||
private function calculateKyungdongBom(
|
||||
private function calculateTenantBom(
|
||||
TenantFormulaHandler $handler,
|
||||
string $finishedGoodsCode,
|
||||
array $inputVariables,
|
||||
int $tenantId
|
||||
): array {
|
||||
$this->addDebugStep(0, '경동전용계산', [
|
||||
$handlerClass = get_class($handler);
|
||||
$this->addDebugStep(0, '테넌트전용계산', [
|
||||
'tenant_id' => $tenantId,
|
||||
'handler' => 'KyungdongFormulaHandler',
|
||||
'handler' => class_basename($handlerClass),
|
||||
'handler_class' => $handlerClass,
|
||||
'finished_goods' => $finishedGoodsCode,
|
||||
]);
|
||||
|
||||
@@ -1617,7 +1634,7 @@ private function calculateKyungdongBom(
|
||||
'item_category' => $finishedGoods['item_category'] ?? 'N/A',
|
||||
]);
|
||||
} else {
|
||||
// 경동 전용: 완제품 미등록 상태에서도 견적 계산 진행
|
||||
// 테넌트 전용: 완제품 미등록 상태에서도 견적 계산 진행
|
||||
$finishedGoods = [
|
||||
'code' => $finishedGoodsCode,
|
||||
'name' => $finishedGoodsCode,
|
||||
@@ -1625,12 +1642,11 @@ private function calculateKyungdongBom(
|
||||
];
|
||||
$this->addDebugStep(2, '완제품선택', [
|
||||
'code' => $finishedGoodsCode,
|
||||
'note' => '경동 전용 계산 - 완제품 미등록 상태로 진행',
|
||||
'note' => '테넌트 전용 계산 - 완제품 미등록 상태로 진행',
|
||||
]);
|
||||
}
|
||||
|
||||
// KyungdongFormulaHandler 인스턴스 생성
|
||||
$handler = new KyungdongFormulaHandler;
|
||||
// 핸들러는 파라미터로 이미 전달됨 (FormulaHandlerFactory가 생성)
|
||||
|
||||
// Step 3: 경동 전용 변수 계산 (제품타입별 면적/중량 공식)
|
||||
$W1 = $W0 + 160;
|
||||
@@ -1672,12 +1688,14 @@ private function calculateKyungdongBom(
|
||||
?? $inputVariables['bracket_size']
|
||||
?? $handler->calculateBracketSize($weight, $bracketInch);
|
||||
|
||||
// 핸들러가 필요한 키 보장 (inputVariables에서 전달되지 않으면 기본값)
|
||||
$productModel = $inputVariables['product_model'] ?? 'KSS01';
|
||||
$finishingType = $inputVariables['finishing_type'] ?? 'SUS';
|
||||
// 핸들러가 필요한 키 보장
|
||||
// 우선순위: 입력값 > FG 코드 파싱 > 기본값
|
||||
$fgParsed = $this->parseFgCode($finishedGoodsCode);
|
||||
$productModel = $inputVariables['product_model'] ?? $fgParsed['model'] ?? 'KSS01';
|
||||
$finishingType = $inputVariables['finishing_type'] ?? $fgParsed['finishing'] ?? 'SUS';
|
||||
|
||||
// 가이드레일 설치타입: 프론트 GT(wall/floor/mixed) → installation_type(벽면형/측면형/혼합형) 매핑
|
||||
$installationType = $inputVariables['installation_type'] ?? match ($inputVariables['GT'] ?? 'wall') {
|
||||
// 가이드레일 설치타입: 입력값 > FG파싱 > GT매핑 > 기본값
|
||||
$installationType = $inputVariables['installation_type'] ?? $fgParsed['installation'] ?? match ($inputVariables['GT'] ?? 'wall') {
|
||||
'floor' => '측면형',
|
||||
'mixed' => '혼합형',
|
||||
default => '벽면형',
|
||||
@@ -1760,7 +1778,7 @@ private function calculateKyungdongBom(
|
||||
],
|
||||
]);
|
||||
|
||||
// Step 4-7: 동적 항목 계산 (KyungdongFormulaHandler 사용)
|
||||
// Step 4-7: 동적 항목 계산 (TenantFormulaHandler 사용)
|
||||
$dynamicItems = $handler->calculateDynamicItems($calculatedVariables);
|
||||
|
||||
$this->addDebugStep(4, 'BOM전개', [
|
||||
@@ -1800,7 +1818,7 @@ private function calculateKyungdongBom(
|
||||
'total_price' => $item['total_price'],
|
||||
'category_group' => $item['category'],
|
||||
'process_group' => $item['category'],
|
||||
'calculation_note' => '경동기업 전용 계산',
|
||||
'calculation_note' => '테넌트 전용 계산',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1818,7 +1836,7 @@ private function calculateKyungdongBom(
|
||||
$category = $item['category_group'];
|
||||
if (! isset($groupedItems[$category])) {
|
||||
$groupedItems[$category] = [
|
||||
'name' => $this->getKyungdongCategoryName($category),
|
||||
'name' => $this->getTenantCategoryName($category),
|
||||
'items' => [],
|
||||
'subtotal' => 0,
|
||||
];
|
||||
@@ -1876,14 +1894,14 @@ private function calculateKyungdongBom(
|
||||
'subtotals' => $subtotals,
|
||||
'grand_total' => $grandTotal,
|
||||
'debug_steps' => $this->debugSteps,
|
||||
'calculation_type' => 'kyungdong',
|
||||
'calculation_type' => 'tenant_handler',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 경동기업 카테고리명 반환
|
||||
* 테넌트 핸들러용 카테고리명 반환
|
||||
*/
|
||||
private function getKyungdongCategoryName(string $category): string
|
||||
private function getTenantCategoryName(string $category): string
|
||||
{
|
||||
return match ($category) {
|
||||
'material' => '주자재',
|
||||
|
||||
38
app/Services/Quote/FormulaHandlerFactory.php
Normal file
38
app/Services/Quote/FormulaHandlerFactory.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Quote;
|
||||
|
||||
use App\Services\Quote\Contracts\TenantFormulaHandler;
|
||||
|
||||
/**
|
||||
* 테넌트별 수식 핸들러 팩토리 (Zero Config)
|
||||
*
|
||||
* tenant_id 기반 자동 발견: Handlers/Tenant{id}/FormulaHandler.php 존재 여부로 라우팅.
|
||||
* 설정 파일, DB 매핑, options 없이 클래스 존재 여부만으로 핸들러를 결정한다.
|
||||
*
|
||||
* 새 업체 추가: Handlers/Tenant{id}/FormulaHandler.php 1개만 생성하면 자동 인식.
|
||||
*/
|
||||
class FormulaHandlerFactory
|
||||
{
|
||||
/**
|
||||
* 테넌트 전용 핸들러 생성 (없으면 null → Generic DB 경로)
|
||||
*/
|
||||
public static function make(int $tenantId): ?TenantFormulaHandler
|
||||
{
|
||||
$class = "App\\Services\\Quote\\Handlers\\Tenant{$tenantId}\\FormulaHandler";
|
||||
|
||||
if (! class_exists($class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$handler = new $class;
|
||||
|
||||
if (! $handler instanceof TenantFormulaHandler) {
|
||||
throw new \RuntimeException(
|
||||
"Handler [{$class}] must implement " . TenantFormulaHandler::class
|
||||
);
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
}
|
||||
1133
app/Services/Quote/Handlers/Tenant287/FormulaHandler.php
Normal file
1133
app/Services/Quote/Handlers/Tenant287/FormulaHandler.php
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user