Files
sam-api/app/Services/ModelSet/ModelSetService.php

489 lines
17 KiB
PHP
Raw Normal View History

<?php
namespace App\Services\ModelSet;
use App\Models\Commons\Category;
use App\Models\Commons\CategoryField;
use App\Models\Design\BomTemplate;
use App\Models\Design\Model;
use App\Models\Design\ModelVersion;
use App\Models\Products\Product;
use App\Services\Calculation\CalculationEngine;
use App\Services\Service;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class ModelSetService extends Service
{
protected CalculationEngine $calculationEngine;
public function __construct(CalculationEngine $calculationEngine)
{
parent::__construct();
$this->calculationEngine = $calculationEngine;
}
/**
* 모델셋 목록 조회 (카테고리 기반)
*/
public function getModelSets(array $filters = []): Collection
{
$query = Category::with(['fields', 'children'])
->where('tenant_id', $this->tenantId())
->where('code_group', 'estimate')
->where('level', '>=', 2); // 루트 카테고리 제외
if (! empty($filters['category_type'])) {
$query->where('code', $filters['category_type']);
}
if (! empty($filters['is_active'])) {
$query->where('is_active', $filters['is_active']);
}
return $query->orderBy('sort_order')->get();
}
/**
* 모델셋 상세 조회
*/
public function getModelSetDetail($categoryId): array
{
$category = Category::with(['fields', 'parent', 'children'])
->where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
// 해당 카테고리의 제품들
$products = Product::where('tenant_id', $this->tenantId())
->where('category_id', $categoryId)
->with(['components'])
->get();
// 해당 카테고리의 모델 및 BOM 템플릿들
$models = $this->getRelatedModels($categoryId);
return [
'category' => $category,
'products' => $products,
'models' => $models,
'field_schema' => $this->generateFieldSchema($category->fields),
];
}
/**
* 새로운 모델셋 생성
*/
public function createModelSet(array $data): array
{
return DB::transaction(function () use ($data) {
// 1. 카테고리 생성
$category = Category::create([
'tenant_id' => $this->tenantId(),
'parent_id' => $data['parent_id'] ?? null,
'code_group' => 'estimate',
'code' => $data['code'],
'name' => $data['name'],
'description' => $data['description'] ?? '',
'level' => $data['level'] ?? 2,
'sort_order' => $data['sort_order'] ?? 999,
'profile_code' => $data['profile_code'] ?? 'custom_category',
'is_active' => $data['is_active'] ?? true,
'created_by' => $this->apiUserId(),
]);
// 2. 동적 필드 생성
if (! empty($data['fields'])) {
foreach ($data['fields'] as $fieldData) {
CategoryField::create([
'tenant_id' => $this->tenantId(),
'category_id' => $category->id,
'field_key' => $fieldData['key'],
'field_name' => $fieldData['name'],
'field_type' => $fieldData['type'],
'is_required' => $fieldData['required'] ?? false,
'sort_order' => $fieldData['order'] ?? 999,
'default_value' => $fieldData['default'] ?? null,
'options' => $fieldData['options'] ?? null,
'description' => $fieldData['description'] ?? '',
'created_by' => $this->apiUserId(),
]);
}
}
// 3. 모델 및 BOM 템플릿 생성 (선택사항)
if (! empty($data['create_model'])) {
$this->createDefaultModel($category, $data['model_data'] ?? []);
}
return $this->getModelSetDetail($category->id);
});
}
/**
* 모델셋 수정
*/
public function updateModelSet($categoryId, array $data): array
{
return DB::transaction(function () use ($categoryId, $data) {
$category = Category::where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
// 카테고리 정보 업데이트
$category->update([
'name' => $data['name'] ?? $category->name,
'description' => $data['description'] ?? $category->description,
'sort_order' => $data['sort_order'] ?? $category->sort_order,
'is_active' => $data['is_active'] ?? $category->is_active,
'updated_by' => $this->apiUserId(),
]);
// 필드 업데이트 (기존 필드 삭제 후 재생성)
if (isset($data['fields'])) {
CategoryField::where('category_id', $categoryId)->delete();
foreach ($data['fields'] as $fieldData) {
CategoryField::create([
'tenant_id' => $this->tenantId(),
'category_id' => $categoryId,
'field_key' => $fieldData['key'],
'field_name' => $fieldData['name'],
'field_type' => $fieldData['type'],
'is_required' => $fieldData['required'] ?? false,
'sort_order' => $fieldData['order'] ?? 999,
'default_value' => $fieldData['default'] ?? null,
'options' => $fieldData['options'] ?? null,
'description' => $fieldData['description'] ?? '',
'created_by' => $this->apiUserId(),
]);
}
}
return $this->getModelSetDetail($categoryId);
});
}
/**
* 모델셋 삭제
*/
public function deleteModelSet($categoryId): void
{
DB::transaction(function () use ($categoryId) {
$category = Category::where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
// 연관된 데이터들 확인
$hasProducts = Product::where('category_id', $categoryId)->exists();
$hasChildren = Category::where('parent_id', $categoryId)->exists();
if ($hasProducts || $hasChildren) {
throw new \Exception(__('error.modelset.has_dependencies'));
}
// 필드 삭제
CategoryField::where('category_id', $categoryId)->delete();
// 카테고리 삭제 (소프트 삭제)
$category->update(['deleted_by' => $this->apiUserId()]);
$category->delete();
});
}
/**
* 모델셋 복제
*/
public function cloneModelSet($categoryId, array $data): array
{
return DB::transaction(function () use ($categoryId, $data) {
$originalCategory = Category::with('fields')
->where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
// 새로운 카테고리 생성
$newCategory = Category::create([
'tenant_id' => $this->tenantId(),
'parent_id' => $originalCategory->parent_id,
'code_group' => $originalCategory->code_group,
'code' => $data['code'],
'name' => $data['name'],
'description' => $data['description'] ?? $originalCategory->description,
'level' => $originalCategory->level,
'sort_order' => $data['sort_order'] ?? 999,
'profile_code' => $originalCategory->profile_code,
'is_active' => $data['is_active'] ?? true,
'created_by' => $this->apiUserId(),
]);
// 필드 복제
foreach ($originalCategory->fields as $field) {
CategoryField::create([
'tenant_id' => $this->tenantId(),
'category_id' => $newCategory->id,
'field_key' => $field->field_key,
'field_name' => $field->field_name,
'field_type' => $field->field_type,
'is_required' => $field->is_required,
'sort_order' => $field->sort_order,
'default_value' => $field->default_value,
'options' => $field->options,
'description' => $field->description,
'created_by' => $this->apiUserId(),
]);
}
return $this->getModelSetDetail($newCategory->id);
});
}
/**
* 모델셋의 카테고리 필드 구조 조회
*/
public function getModelSetCategoryFields($categoryId): array
{
$category = Category::with('fields')
->where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
return $this->generateFieldSchema($category->fields);
}
/**
* 모델셋의 BOM 템플릿 목록
*/
public function getModelSetBomTemplates($categoryId): Collection
{
// 해당 카테고리와 연관된 모델들의 BOM 템플릿들
$models = $this->getRelatedModels($categoryId);
$bomTemplates = collect();
foreach ($models as $model) {
foreach ($model['versions'] as $version) {
$bomTemplates = $bomTemplates->merge($version['bom_templates']);
}
}
return $bomTemplates;
}
/**
* 견적 파라미터 조회 (동적 필드 기반)
*/
public function getEstimateParameters($categoryId, array $filters = []): array
{
$category = Category::with('fields')
->where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
// 입력 파라미터 (사용자가 입력해야 하는 필드들)
$inputFields = $category->fields
->filter(function ($field) {
return in_array($field->field_key, [
'open_width', 'open_height', 'quantity',
'model_name', 'guide_rail_type', 'shutter_box',
]);
})
->map(function ($field) {
return [
'key' => $field->field_key,
'name' => $field->field_name,
'type' => $field->field_type,
'required' => $field->is_required,
'default' => $field->default_value,
'options' => $field->options,
'description' => $field->description,
];
});
// 계산 결과 필드 (자동으로 계산되는 필드들)
$calculatedFields = $category->fields
->filter(function ($field) {
return in_array($field->field_key, [
'make_width', 'make_height', 'calculated_weight',
'calculated_area', 'motor_bracket_size', 'motor_capacity',
]);
})
->map(function ($field) {
return [
'key' => $field->field_key,
'name' => $field->field_name,
'type' => $field->field_type,
'description' => $field->description,
];
});
return [
'category' => [
'id' => $category->id,
'name' => $category->name,
'code' => $category->code,
],
'input_fields' => $inputFields->values(),
'calculated_fields' => $calculatedFields->values(),
'calculation_schema' => $this->getCalculationSchema($category->code),
];
}
/**
* 모델셋 기반 BOM 계산
*/
public function calculateModelSetBom($categoryId, array $parameters): array
{
$category = Category::where('tenant_id', $this->tenantId())
->findOrFail($categoryId);
// BOM 템플릿 찾기 (기본 템플릿 사용)
$bomTemplate = $this->findDefaultBomTemplate($categoryId, $parameters);
if (! $bomTemplate) {
throw new \Exception(__('error.bom_template.not_found'));
}
// 기존 BOM 계산 엔진 사용
return $this->calculationEngine->calculateBOM(
$bomTemplate->id,
$parameters,
$this->getCompanyName($category)
);
}
/**
* 카테고리와 연관된 모델들 조회
*/
protected function getRelatedModels($categoryId): Collection
{
// 카테고리 코드 기반으로 모델 찾기
$category = Category::findOrFail($categoryId);
return Model::with(['versions.bomTemplates'])
->where('tenant_id', $this->tenantId())
->where('code', 'like', $category->code.'%')
->get()
->map(function ($model) {
return [
'id' => $model->id,
'code' => $model->code,
'name' => $model->name,
'versions' => $model->versions->map(function ($version) {
return [
'id' => $version->id,
'version_no' => $version->version_no,
'status' => $version->status,
'bom_templates' => $version->bomTemplates,
];
}),
];
});
}
/**
* 필드 스키마 생성
*/
protected function generateFieldSchema(Collection $fields): array
{
return $fields->map(function ($field) {
return [
'key' => $field->field_key,
'name' => $field->field_name,
'type' => $field->field_type,
'required' => $field->is_required,
'order' => $field->sort_order,
'default' => $field->default_value,
'options' => $field->options,
'description' => $field->description,
];
})->sortBy('order')->values()->toArray();
}
/**
* 기본 모델 생성
*/
protected function createDefaultModel(Category $category, array $modelData): void
{
$model = Model::create([
'tenant_id' => $this->tenantId(),
'code' => $modelData['code'] ?? $category->code.'_MODEL',
'name' => $modelData['name'] ?? $category->name.' 기본 모델',
'description' => $modelData['description'] ?? '',
'status' => 'DRAFT',
'created_by' => $this->apiUserId(),
]);
$version = ModelVersion::create([
'tenant_id' => $this->tenantId(),
'model_id' => $model->id,
'version_no' => 'v1.0',
'status' => 'DRAFT',
'created_by' => $this->apiUserId(),
]);
BomTemplate::create([
'tenant_id' => $this->tenantId(),
'model_version_id' => $version->id,
'name' => $category->name.' 기본 BOM',
'company_type' => $this->getCompanyName($category),
'formula_version' => 'v1.0',
'calculation_schema' => $this->getDefaultCalculationSchema($category),
'created_by' => $this->apiUserId(),
]);
}
/**
* 계산 스키마 조회
*/
protected function getCalculationSchema(string $categoryCode): array
{
if ($categoryCode === 'screen_product') {
return [
'size_calculation' => 'kyungdong_screen_size',
'weight_calculation' => 'screen_weight_calculation',
'bracket_calculation' => 'motor_bracket_size',
];
} elseif ($categoryCode === 'steel_product') {
return [
'size_calculation' => 'kyungdong_steel_size',
'bracket_calculation' => 'motor_bracket_size',
'round_bar_calculation' => 'round_bar_quantity',
];
}
return [];
}
/**
* 기본 BOM 템플릿 찾기
*/
protected function findDefaultBomTemplate($categoryId, array $parameters): ?BomTemplate
{
$models = $this->getRelatedModels($categoryId);
foreach ($models as $model) {
foreach ($model['versions'] as $version) {
if ($version['status'] === 'RELEASED' && $version['bom_templates']->isNotEmpty()) {
return $version['bom_templates']->first();
}
}
}
return null;
}
/**
* 업체명 조회
*/
protected function getCompanyName(Category $category): string
{
// 테넌트 정보에서 업체명 조회하거나 기본값 사용
return '경동기업'; // 임시 하드코딩
}
/**
* 기본 계산 스키마 생성
*/
protected function getDefaultCalculationSchema(Category $category): array
{
return [
'calculation_type' => $category->code,
'formulas' => $this->getCalculationSchema($category->code),
];
}
}