- 데이터베이스 스키마 확장: BOM 테이블에 계산 관련 필드 추가 - 계산 엔진 구현: CalculationEngine, FormulaParser, ParameterValidator - API 구현: 견적 파라미터 추출, 실시간 BOM 계산, 업체별 산출식 관리 - FormRequest 검증: 모든 입력 데이터 검증 및 한국어 에러 메시지 - 라우트 등록: 5개 BOM 계산 API 엔드포인트 추가 주요 기능: • BOM에서 필요한 조건만 동적 추출하여 견적 화면에 표시 • 경동기업 하드코딩 산출식을 동적 시스템으로 전환 • 업체별 산출식 버전 관리 및 실시간 테스트 지원 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
268 lines
9.3 KiB
PHP
268 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Calculation;
|
|
|
|
use App\Models\Design\BomTemplate;
|
|
use App\Models\Design\BomTemplateItem;
|
|
use App\Models\Calculation\CalculationConfig;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class CalculationEngine
|
|
{
|
|
protected FormulaParser $parser;
|
|
protected ParameterValidator $validator;
|
|
|
|
public function __construct(FormulaParser $parser, ParameterValidator $validator)
|
|
{
|
|
$this->parser = $parser;
|
|
$this->validator = $validator;
|
|
}
|
|
|
|
/**
|
|
* BOM 계산 실행
|
|
* @param int $bomTemplateId BOM 템플릿 ID
|
|
* @param array $parameters 입력 파라미터
|
|
* @param string|null $companyName 업체명 (null시 기본값 사용)
|
|
* @return array 계산 결과
|
|
*/
|
|
public function calculateBOM(int $bomTemplateId, array $parameters, ?string $companyName = null): array
|
|
{
|
|
try {
|
|
// BOM 템플릿 조회
|
|
$bomTemplate = BomTemplate::with(['items', 'modelVersion.model'])
|
|
->findOrFail($bomTemplateId);
|
|
|
|
// 파라미터 검증
|
|
$this->validateParameters($bomTemplate, $parameters);
|
|
|
|
// 중간 계산값 도출 (W1, H1, 면적, 중량 등)
|
|
$calculatedValues = $this->calculateIntermediateValues($bomTemplate, $parameters, $companyName);
|
|
|
|
// BOM 아이템별 수량 계산
|
|
$bomItems = $this->calculateBomItems($bomTemplate->items, $calculatedValues, $parameters, $companyName);
|
|
|
|
return [
|
|
'success' => true,
|
|
'bom_template' => [
|
|
'id' => $bomTemplate->id,
|
|
'name' => $bomTemplate->name,
|
|
'company_type' => $bomTemplate->company_type,
|
|
'formula_version' => $bomTemplate->formula_version
|
|
],
|
|
'input_parameters' => $parameters,
|
|
'calculated_values' => $calculatedValues,
|
|
'bom_items' => $bomItems,
|
|
'calculation_timestamp' => now()
|
|
];
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('BOM 계산 실패', [
|
|
'bom_template_id' => $bomTemplateId,
|
|
'parameters' => $parameters,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return [
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
'bom_template_id' => $bomTemplateId
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 견적시 필요한 파라미터 스키마 추출
|
|
* @param int $bomTemplateId BOM 템플릿 ID
|
|
* @return array 파라미터 스키마
|
|
*/
|
|
public function getRequiredParameters(int $bomTemplateId): array
|
|
{
|
|
$bomTemplate = BomTemplate::findOrFail($bomTemplateId);
|
|
|
|
$schema = $bomTemplate->calculation_schema;
|
|
if (!$schema) {
|
|
return $this->getDefaultParameterSchema($bomTemplate);
|
|
}
|
|
|
|
return $schema;
|
|
}
|
|
|
|
/**
|
|
* 중간 계산값 도출 (W1, H1, 면적, 중량 등)
|
|
*/
|
|
protected function calculateIntermediateValues(BomTemplate $bomTemplate, array $parameters, ?string $companyName): array
|
|
{
|
|
$company = $companyName ?: $bomTemplate->company_type;
|
|
$calculated = [];
|
|
|
|
// 기본 파라미터에서 추출
|
|
$W0 = $parameters['W0'] ?? 0;
|
|
$H0 = $parameters['H0'] ?? 0;
|
|
$productType = $parameters['product_type'] ?? 'screen';
|
|
|
|
// 업체별 제작사이즈 계산
|
|
$sizeFormula = $this->getCalculationConfig($company, 'manufacturing_size');
|
|
if ($sizeFormula) {
|
|
$calculated = array_merge($calculated, $this->parser->execute($sizeFormula->formula_expression, [
|
|
'W0' => $W0,
|
|
'H0' => $H0,
|
|
'product_type' => $productType
|
|
]));
|
|
} else {
|
|
// 기본 공식 (경동기업 기준)
|
|
if ($productType === 'screen') {
|
|
$calculated['W1'] = $W0 + 160;
|
|
$calculated['H1'] = $H0 + 350;
|
|
} else {
|
|
$calculated['W1'] = $W0 + 110;
|
|
$calculated['H1'] = $H0 + 350;
|
|
}
|
|
}
|
|
|
|
// 면적 계산
|
|
$calculated['area'] = ($calculated['W1'] * $calculated['H1']) / 1000000;
|
|
|
|
// 중량 계산
|
|
$weightFormula = $this->getCalculationConfig($company, 'weight_calculation');
|
|
if ($weightFormula) {
|
|
$weightResult = $this->parser->execute($weightFormula->formula_expression, array_merge($parameters, $calculated));
|
|
$calculated['weight'] = $weightResult['weight'] ?? 0;
|
|
} else {
|
|
// 기본 중량 공식 (스크린)
|
|
if ($productType === 'screen') {
|
|
$calculated['weight'] = ($calculated['area'] * 2) + ($W0 / 1000 * 14.17);
|
|
} else {
|
|
$calculated['weight'] = $calculated['area'] * 40;
|
|
}
|
|
}
|
|
|
|
return $calculated;
|
|
}
|
|
|
|
/**
|
|
* BOM 아이템별 수량 계산
|
|
*/
|
|
protected function calculateBomItems(Collection $bomItems, array $calculatedValues, array $parameters, ?string $companyName): array
|
|
{
|
|
$results = [];
|
|
|
|
foreach ($bomItems as $item) {
|
|
if (!$item->is_calculated) {
|
|
// 고정 수량 아이템
|
|
$results[] = [
|
|
'item_id' => $item->id,
|
|
'ref_type' => $item->ref_type,
|
|
'ref_id' => $item->ref_id,
|
|
'original_qty' => $item->qty,
|
|
'calculated_qty' => $item->qty,
|
|
'is_calculated' => false,
|
|
'calculation_formula' => null
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// 계산식 적용 아이템
|
|
try {
|
|
$allParameters = array_merge($parameters, $calculatedValues);
|
|
$calculatedQty = $this->parser->execute($item->calculation_formula, $allParameters);
|
|
|
|
$results[] = [
|
|
'item_id' => $item->id,
|
|
'ref_type' => $item->ref_type,
|
|
'ref_id' => $item->ref_id,
|
|
'original_qty' => $item->qty,
|
|
'calculated_qty' => $calculatedQty['result'] ?? $item->qty,
|
|
'is_calculated' => true,
|
|
'calculation_formula' => $item->calculation_formula,
|
|
'depends_on' => $item->depends_on
|
|
];
|
|
} catch (\Exception $e) {
|
|
Log::warning('BOM 아이템 계산 실패', [
|
|
'item_id' => $item->id,
|
|
'formula' => $item->calculation_formula,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
// 계산 실패시 원래 수량 사용
|
|
$results[] = [
|
|
'item_id' => $item->id,
|
|
'ref_type' => $item->ref_type,
|
|
'ref_id' => $item->ref_id,
|
|
'original_qty' => $item->qty,
|
|
'calculated_qty' => $item->qty,
|
|
'is_calculated' => false,
|
|
'calculation_error' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* 파라미터 검증
|
|
*/
|
|
protected function validateParameters(BomTemplate $bomTemplate, array $parameters): void
|
|
{
|
|
$schema = $bomTemplate->calculation_schema;
|
|
if (!$schema) return;
|
|
|
|
$this->validator->validate($schema, $parameters);
|
|
}
|
|
|
|
/**
|
|
* 업체별 산출식 설정 조회
|
|
*/
|
|
protected function getCalculationConfig(string $companyName, string $formulaType): ?CalculationConfig
|
|
{
|
|
return CalculationConfig::where('company_name', $companyName)
|
|
->where('formula_type', $formulaType)
|
|
->where('is_active', true)
|
|
->latest('version')
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* 기본 파라미터 스키마 생성
|
|
*/
|
|
protected function getDefaultParameterSchema(BomTemplate $bomTemplate): array
|
|
{
|
|
return [
|
|
'required_parameters' => [
|
|
[
|
|
'key' => 'W0',
|
|
'label' => '오픈사이즈 가로(mm)',
|
|
'type' => 'integer',
|
|
'required' => true,
|
|
'min' => 500,
|
|
'max' => 15000
|
|
],
|
|
[
|
|
'key' => 'H0',
|
|
'label' => '오픈사이즈 세로(mm)',
|
|
'type' => 'integer',
|
|
'required' => true,
|
|
'min' => 500,
|
|
'max' => 5000
|
|
],
|
|
[
|
|
'key' => 'product_type',
|
|
'label' => '제품타입',
|
|
'type' => 'select',
|
|
'options' => ['screen' => '스크린', 'steel' => '철재'],
|
|
'required' => true
|
|
],
|
|
[
|
|
'key' => 'installation_type',
|
|
'label' => '설치방식',
|
|
'type' => 'select',
|
|
'options' => ['wall' => '벽면형', 'side' => '측면형', 'mixed' => '혼합형'],
|
|
'required' => false
|
|
]
|
|
],
|
|
'company_type' => $bomTemplate->company_type ?: 'default',
|
|
'formula_version' => $bomTemplate->formula_version ?: 'v1.0'
|
|
];
|
|
}
|
|
} |