Files
sam-api/app/Services/Design/BomCalculationService.php
hskwon bd678dfea9 feat: 업체별 동적 BOM 계산 시스템 구현
- 데이터베이스 스키마 확장: 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>
2025-09-22 22:09:42 +09:00

265 lines
9.2 KiB
PHP

<?php
namespace App\Services\Design;
use App\Services\Service;
use App\Services\Calculation\CalculationEngine;
use App\Models\Design\BomTemplate;
use App\Models\Calculation\CalculationConfig;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BomCalculationService extends Service
{
protected CalculationEngine $calculationEngine;
public function __construct(CalculationEngine $calculationEngine)
{
$this->calculationEngine = $calculationEngine;
}
/**
* 견적 파라미터 조회
* @param int $modelId 모델 ID
* @param string|null $companyName 업체명
* @return array
*/
public function getEstimateParameters(int $modelId, ?string $companyName = null): array
{
try {
// 모델의 기본 BOM 템플릿 조회
$bomTemplate = BomTemplate::with(['modelVersion.model'])
->where('tenant_id', $this->tenantId())
->whereHas('modelVersion', function ($query) use ($modelId) {
$query->whereHas('model', function ($q) use ($modelId) {
$q->where('id', $modelId);
});
})
->where('is_primary', true)
->first();
if (!$bomTemplate) {
throw new \Exception("모델 ID {$modelId}에 대한 BOM 템플릿을 찾을 수 없습니다.");
}
// 파라미터 스키마 추출
$parameters = $this->calculationEngine->getRequiredParameters($bomTemplate->id);
// 업체별 추가 파라미터가 있는지 확인
if ($companyName) {
$companyParameters = $this->getCompanySpecificParameters($companyName);
if ($companyParameters) {
$parameters = array_merge_recursive($parameters, $companyParameters);
}
}
return [
'model_info' => [
'id' => $bomTemplate->modelVersion->model->id,
'name' => $bomTemplate->modelVersion->model->name ?? '모델명 없음',
'version' => $bomTemplate->modelVersion->version_no ?? 'v1.0',
'bom_template_id' => $bomTemplate->id
],
'company_info' => [
'company_type' => $bomTemplate->company_type,
'formula_version' => $bomTemplate->formula_version,
'requested_company' => $companyName
],
'parameters' => $parameters,
'calculation_preview' => true
];
} catch (\Exception $e) {
Log::error('견적 파라미터 조회 실패', [
'model_id' => $modelId,
'company_name' => $companyName,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* BOM 계산 실행
* @param int $bomTemplateId BOM 템플릿 ID
* @param array $parameters 입력 파라미터
* @param string|null $companyName 업체명
* @return array
*/
public function calculateBomEstimate(int $bomTemplateId, array $parameters, ?string $companyName = null): array
{
try {
DB::beginTransaction();
// BOM 템플릿 조회 및 권한 확인
$bomTemplate = BomTemplate::where('tenant_id', $this->tenantId())
->findOrFail($bomTemplateId);
// 업체명이 지정된 경우 BOM 템플릿 업데이트
if ($companyName && $bomTemplate->company_type !== $companyName) {
$bomTemplate->update(['company_type' => $companyName]);
}
// 계산 엔진 실행
$result = $this->calculationEngine->calculateBOM($bomTemplateId, $parameters, $companyName);
// 계산 이력 로그 저장 (선택사항)
$this->logCalculationHistory($bomTemplateId, $parameters, $result, $companyName);
DB::commit();
return [
'success' => $result['success'],
'data' => $result['success'] ? $result : null,
'error' => $result['success'] ? null : $result['error']
];
} catch (\Exception $e) {
DB::rollBack();
Log::error('BOM 계산 실행 실패', [
'bom_template_id' => $bomTemplateId,
'parameters' => $parameters,
'company_name' => $companyName,
'error' => $e->getMessage()
]);
return [
'success' => false,
'data' => null,
'error' => $e->getMessage()
];
}
}
/**
* 업체별 산출식 목록 조회
* @param string $companyName 업체명
* @return array
*/
public function getCompanyFormulas(string $companyName): array
{
$formulas = CalculationConfig::getActiveFormulasForCompany($this->tenantId(), $companyName);
return [
'company_name' => $companyName,
'total_formulas' => count($formulas),
'formulas' => array_map(function ($formula) {
return [
'type' => $formula['formula_type'],
'version' => $formula['version'],
'description' => $formula['description'],
'parameters' => $formula['parameters'],
'updated_at' => $formula['updated_at']
];
}, $formulas)
];
}
/**
* 업체별 산출식 등록/수정
* @param string $companyName 업체명
* @param string $formulaType 산출식 타입
* @param array $formulaData 산출식 데이터
* @return array
*/
public function saveCompanyFormula(string $companyName, string $formulaType, array $formulaData): array
{
try {
DB::beginTransaction();
// 기존 산출식이 있는지 확인
$existingFormula = CalculationConfig::getLatestFormula($this->tenantId(), $companyName, $formulaType);
if ($existingFormula) {
// 버전 업그레이드
$newVersion = $this->incrementVersion($existingFormula->version);
$existingFormula->update(['is_active' => false]); // 기존 비활성화
} else {
$newVersion = 'v1.0';
}
// 새 산출식 생성
$formula = CalculationConfig::create([
'tenant_id' => $this->tenantId(),
'company_name' => $companyName,
'formula_type' => $formulaType,
'version' => $newVersion,
'formula_expression' => $formulaData['formula_expression'],
'parameters' => $formulaData['parameters'],
'conditions' => $formulaData['conditions'] ?? null,
'validation_rules' => $formulaData['validation_rules'] ?? null,
'description' => $formulaData['description'] ?? null,
'is_active' => true,
'created_by' => $this->apiUserId()
]);
DB::commit();
return [
'id' => $formula->id,
'company_name' => $formula->company_name,
'formula_type' => $formula->formula_type,
'version' => $formula->version,
'is_active' => $formula->is_active,
'created_at' => $formula->created_at
];
} catch (\Exception $e) {
DB::rollBack();
Log::error('업체별 산출식 저장 실패', [
'company_name' => $companyName,
'formula_type' => $formulaType,
'error' => $e->getMessage()
]);
throw $e;
}
}
/**
* 업체별 추가 파라미터 조회
*/
protected function getCompanySpecificParameters(string $companyName): ?array
{
// 업체별 특수 파라미터가 있는지 확인
$companyConfig = CalculationConfig::forTenant($this->tenantId())
->forCompany($companyName)
->forType('additional_parameters')
->active()
->latest()
->first();
return $companyConfig ? $companyConfig->parameters : null;
}
/**
* 계산 이력 로그 저장
*/
protected function logCalculationHistory(int $bomTemplateId, array $parameters, array $result, ?string $companyName): void
{
// 필요에 따라 계산 이력을 별도 테이블에 저장
Log::info('BOM 계산 완료', [
'bom_template_id' => $bomTemplateId,
'company_name' => $companyName,
'parameters' => $parameters,
'success' => $result['success'],
'calculated_items_count' => $result['success'] ? count($result['bom_items']) : 0,
'calculated_by' => $this->apiUserId()
]);
}
/**
* 버전 번호 증가
*/
protected function incrementVersion(string $currentVersion): string
{
if (preg_match('/^v(\d+)\.(\d+)$/', $currentVersion, $matches)) {
$major = (int)$matches[1];
$minor = (int)$matches[2];
return "v{$major}." . ($minor + 1);
}
return 'v1.0';
}
}