Revert "feat: DB 연결 오버라이딩 및 대시보드 통계 위젯 추가"

This reverts commit bf8036a64b.
This commit is contained in:
2025-09-30 23:56:25 +09:00
parent bf8036a64b
commit 802a511aa0
81 changed files with 102 additions and 22632 deletions

View File

@@ -1,167 +0,0 @@
<?php
namespace Database\Factories;
use App\Models\BomConditionRule;
use App\Models\Model;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\BomConditionRule>
*/
class BomConditionRuleFactory extends Factory
{
protected $model = BomConditionRule::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'tenant_id' => 1,
'model_id' => Model::factory(),
'name' => $this->faker->words(3, true),
'description' => $this->faker->sentence(),
'condition_expression' => 'area > 5',
'component_code' => 'BRK-001',
'quantity_expression' => '2',
'priority' => $this->faker->numberBetween(1, 100),
'is_active' => true,
'created_by' => 1,
'updated_by' => 1,
];
}
/**
* Screen model condition rules.
*/
public function screenRules(): static
{
return $this->sequence(
[
'name' => '케이스 규칙',
'description' => '크기에 따른 케이스 선택',
'condition_expression' => 'area <= 3',
'component_code' => 'CASE-SMALL',
'quantity_expression' => '1',
'priority' => 10,
],
[
'name' => '케이스 규칙 (중형)',
'description' => '중형 크기 케이스',
'condition_expression' => 'area > 3 AND area <= 6',
'component_code' => 'CASE-MEDIUM',
'quantity_expression' => '1',
'priority' => 11,
],
[
'name' => '케이스 규칙 (대형)',
'description' => '대형 크기 케이스',
'condition_expression' => 'area > 6',
'component_code' => 'CASE-LARGE',
'quantity_expression' => '1',
'priority' => 12,
],
[
'name' => '바텀 규칙',
'description' => '바텀 개수',
'condition_expression' => 'TRUE',
'component_code' => 'BOTTOM-001',
'quantity_expression' => 'CEIL(W1 / 1000)',
'priority' => 20,
],
[
'name' => '샤프트 규칙',
'description' => '샤프트 길이',
'condition_expression' => 'TRUE',
'component_code' => 'SHAFT-001',
'quantity_expression' => 'W1 / 1000',
'priority' => 30,
],
[
'name' => '파이프 규칙',
'description' => '파이프 길이',
'condition_expression' => 'screen_type = "SCREEN"',
'component_code' => 'PIPE-SCREEN',
'quantity_expression' => 'W1 / 1000',
'priority' => 40,
],
[
'name' => '슬라트 파이프 규칙',
'description' => '슬라트용 파이프',
'condition_expression' => 'screen_type = "SLAT"',
'component_code' => 'PIPE-SLAT',
'quantity_expression' => 'W1 / 1000',
'priority' => 41,
]
);
}
/**
* Steel model condition rules.
*/
public function steelRules(): static
{
return $this->sequence(
[
'name' => '프레임 규칙',
'description' => '프레임 길이',
'condition_expression' => 'TRUE',
'component_code' => 'FRAME-STEEL',
'quantity_expression' => '(W1 + H1) * 2 / 1000',
'priority' => 10,
],
[
'name' => '패널 규칙',
'description' => '패널 개수',
'condition_expression' => 'TRUE',
'component_code' => 'PANEL-STEEL',
'quantity_expression' => 'CEIL(area)',
'priority' => 20,
]
);
}
/**
* High priority rule.
*/
public function highPriority(): static
{
return $this->state(fn (array $attributes) => [
'priority' => $this->faker->numberBetween(1, 10),
]);
}
/**
* Low priority rule.
*/
public function lowPriority(): static
{
return $this->state(fn (array $attributes) => [
'priority' => $this->faker->numberBetween(90, 100),
]);
}
/**
* Active rule.
*/
public function active(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => true,
]);
}
/**
* Inactive rule.
*/
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
}

View File

@@ -1,96 +0,0 @@
<?php
namespace Database\Factories;
use App\Models\Model;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Model>
*/
class ModelFactory extends Factory
{
protected $model = Model::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'tenant_id' => 1,
'code' => 'KSS' . $this->faker->numberBetween(10, 99),
'name' => $this->faker->words(3, true),
'description' => $this->faker->sentence(),
'status' => $this->faker->randomElement(['DRAFT', 'RELEASED', 'ARCHIVED']),
'product_family' => $this->faker->randomElement(['SCREEN', 'STEEL']),
'is_active' => true,
'created_by' => 1,
'updated_by' => 1,
];
}
/**
* Indicate that the model is active.
*/
public function active(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => true,
]);
}
/**
* Indicate that the model is inactive.
*/
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
/**
* Indicate that the model is released.
*/
public function released(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'RELEASED',
]);
}
/**
* Indicate that the model is draft.
*/
public function draft(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'DRAFT',
]);
}
/**
* Screen type model.
*/
public function screen(): static
{
return $this->state(fn (array $attributes) => [
'product_family' => 'SCREEN',
'code' => 'KSS' . $this->faker->numberBetween(10, 99),
]);
}
/**
* Steel type model.
*/
public function steel(): static
{
return $this->state(fn (array $attributes) => [
'product_family' => 'STEEL',
'code' => 'KST' . $this->faker->numberBetween(10, 99),
]);
}
}

View File

@@ -1,151 +0,0 @@
<?php
namespace Database\Factories;
use App\Models\Model;
use App\Models\ModelFormula;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ModelFormula>
*/
class ModelFormulaFactory extends Factory
{
protected $model = ModelFormula::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'tenant_id' => 1,
'model_id' => Model::factory(),
'name' => $this->faker->word(),
'expression' => 'W0 * H0',
'description' => $this->faker->sentence(),
'return_type' => 'NUMBER',
'sort_order' => $this->faker->numberBetween(1, 10),
'is_active' => true,
'created_by' => 1,
'updated_by' => 1,
];
}
/**
* Screen model formulas.
*/
public function screenFormulas(): static
{
return $this->sequence(
[
'name' => 'W1',
'expression' => 'W0 + 120',
'description' => '최종 가로 크기',
'return_type' => 'NUMBER',
'sort_order' => 1,
],
[
'name' => 'H1',
'expression' => 'H0 + 100',
'description' => '최종 세로 크기',
'return_type' => 'NUMBER',
'sort_order' => 2,
],
[
'name' => 'area',
'expression' => 'W1 * H1 / 1000000',
'description' => '면적 (㎡)',
'return_type' => 'NUMBER',
'sort_order' => 3,
],
[
'name' => 'weight',
'expression' => 'area * 15',
'description' => '중량 (kg)',
'return_type' => 'NUMBER',
'sort_order' => 4,
],
[
'name' => 'motor',
'expression' => 'IF(area <= 3, "0.5HP", IF(area <= 6, "1HP", "2HP"))',
'description' => '모터 용량',
'return_type' => 'STRING',
'sort_order' => 5,
],
[
'name' => 'bracket',
'expression' => 'CEIL(W1 / 600)',
'description' => '브라켓 개수',
'return_type' => 'NUMBER',
'sort_order' => 6,
],
[
'name' => 'guide',
'expression' => 'H1 / 1000 * 2',
'description' => '가이드 길이 (m)',
'return_type' => 'NUMBER',
'sort_order' => 7,
]
);
}
/**
* Steel model formulas.
*/
public function steelFormulas(): static
{
return $this->sequence(
[
'name' => 'W1',
'expression' => 'W0 + 50',
'description' => '최종 가로 크기',
'return_type' => 'NUMBER',
'sort_order' => 1,
],
[
'name' => 'H1',
'expression' => 'H0 + 50',
'description' => '최종 세로 크기',
'return_type' => 'NUMBER',
'sort_order' => 2,
],
[
'name' => 'area',
'expression' => 'W1 * H1 / 1000000',
'description' => '면적 (㎡)',
'return_type' => 'NUMBER',
'sort_order' => 3,
],
[
'name' => 'weight',
'expression' => 'area * thickness * 7.85',
'description' => '중량 (kg)',
'return_type' => 'NUMBER',
'sort_order' => 4,
]
);
}
/**
* Number type formula.
*/
public function number(): static
{
return $this->state(fn (array $attributes) => [
'return_type' => 'NUMBER',
]);
}
/**
* String type formula.
*/
public function string(): static
{
return $this->state(fn (array $attributes) => [
'return_type' => 'STRING',
]);
}
}

View File

@@ -1,173 +0,0 @@
<?php
namespace Database\Factories;
use App\Models\Model;
use App\Models\ModelParameter;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ModelParameter>
*/
class ModelParameterFactory extends Factory
{
protected $model = ModelParameter::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'tenant_id' => 1,
'model_id' => Model::factory(),
'name' => $this->faker->word(),
'label' => $this->faker->words(2, true),
'type' => $this->faker->randomElement(['NUMBER', 'SELECT', 'BOOLEAN']),
'default_value' => '0',
'validation_rules' => json_encode(['required' => true]),
'options' => null,
'sort_order' => $this->faker->numberBetween(1, 10),
'is_required' => true,
'is_active' => true,
'created_by' => 1,
'updated_by' => 1,
];
}
/**
* Screen model parameters.
*/
public function screenParameters(): static
{
return $this->sequence(
[
'name' => 'W0',
'label' => '가로(mm)',
'type' => 'NUMBER',
'default_value' => '1000',
'validation_rules' => json_encode(['required' => true, 'numeric' => true, 'min' => 500, 'max' => 3000]),
'sort_order' => 1,
],
[
'name' => 'H0',
'label' => '세로(mm)',
'type' => 'NUMBER',
'default_value' => '800',
'validation_rules' => json_encode(['required' => true, 'numeric' => true, 'min' => 400, 'max' => 2000]),
'sort_order' => 2,
],
[
'name' => 'screen_type',
'label' => '스크린 타입',
'type' => 'SELECT',
'default_value' => 'SCREEN',
'validation_rules' => json_encode(['required' => true, 'in' => ['SCREEN', 'SLAT']]),
'options' => json_encode([
['value' => 'SCREEN', 'label' => '스크린'],
['value' => 'SLAT', 'label' => '슬라트']
]),
'sort_order' => 3,
],
[
'name' => 'install_type',
'label' => '설치 방식',
'type' => 'SELECT',
'default_value' => 'WALL',
'validation_rules' => json_encode(['required' => true, 'in' => ['WALL', 'SIDE', 'MIXED']]),
'options' => json_encode([
['value' => 'WALL', 'label' => '벽면 설치'],
['value' => 'SIDE', 'label' => '측면 설치'],
['value' => 'MIXED', 'label' => '혼합 설치']
]),
'sort_order' => 4,
],
[
'name' => 'power_source',
'label' => '전원',
'type' => 'SELECT',
'default_value' => 'AC',
'validation_rules' => json_encode(['required' => true, 'in' => ['AC', 'DC', 'MANUAL']]),
'options' => json_encode([
['value' => 'AC', 'label' => 'AC 전원'],
['value' => 'DC', 'label' => 'DC 전원'],
['value' => 'MANUAL', 'label' => '수동']
]),
'sort_order' => 5,
]
);
}
/**
* Steel model parameters.
*/
public function steelParameters(): static
{
return $this->sequence(
[
'name' => 'W0',
'label' => '가로(mm)',
'type' => 'NUMBER',
'default_value' => '1200',
'validation_rules' => json_encode(['required' => true, 'numeric' => true, 'min' => 800, 'max' => 4000]),
'sort_order' => 1,
],
[
'name' => 'H0',
'label' => '세로(mm)',
'type' => 'NUMBER',
'default_value' => '1000',
'validation_rules' => json_encode(['required' => true, 'numeric' => true, 'min' => 600, 'max' => 3000]),
'sort_order' => 2,
],
[
'name' => 'thickness',
'label' => '두께(mm)',
'type' => 'NUMBER',
'default_value' => '50',
'validation_rules' => json_encode(['required' => true, 'numeric' => true, 'min' => 20, 'max' => 100]),
'sort_order' => 3,
]
);
}
/**
* Number type parameter.
*/
public function number(): static
{
return $this->state(fn (array $attributes) => [
'type' => 'NUMBER',
'validation_rules' => json_encode(['required' => true, 'numeric' => true]),
'options' => null,
]);
}
/**
* Select type parameter.
*/
public function select(): static
{
return $this->state(fn (array $attributes) => [
'type' => 'SELECT',
'options' => json_encode([
['value' => 'option1', 'label' => 'Option 1'],
['value' => 'option2', 'label' => 'Option 2'],
]),
]);
}
/**
* Boolean type parameter.
*/
public function boolean(): static
{
return $this->state(fn (array $attributes) => [
'type' => 'BOOLEAN',
'default_value' => 'false',
'options' => null,
]);
}
}

View File

@@ -1,50 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('bom_template_groups', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('model_id')->comment('모델 ID');
$table->string('group_name', 100)->comment('그룹명 (본체, 절곡물, 가이드레일 등)');
$table->unsignedBigInteger('parent_group_id')->nullable()->comment('상위 그룹 ID (계층 구조)');
$table->integer('display_order')->default(0)->comment('표시 순서');
$table->text('description')->nullable()->comment('그룹 설명');
$table->boolean('is_active')->default(true)->comment('활성 여부');
// 공통 컬럼
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'model_id']);
$table->index(['tenant_id', 'model_id', 'parent_group_id']);
$table->unique(['tenant_id', 'model_id', 'group_name'], 'unique_group_per_model');
// 외래키 (설계용, 프로덕션에서는 제거 고려)
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->foreign('model_id')->references('id')->on('models')->onDelete('cascade');
$table->foreign('parent_group_id')->references('id')->on('bom_template_groups')->onDelete('set null');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bom_template_groups');
}
};

View File

@@ -1,55 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('model_parameters', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('model_id')->comment('모델 ID');
$table->string('parameter_name', 50)->comment('매개변수명 (W0, H0, screen_type 등)');
$table->enum('parameter_type', ['INTEGER', 'DECIMAL', 'STRING', 'BOOLEAN'])->comment('매개변수 타입');
$table->boolean('is_required')->default(true)->comment('필수 여부');
$table->string('default_value')->nullable()->comment('기본값');
$table->decimal('min_value', 15, 4)->nullable()->comment('최소값 (숫자형)');
$table->decimal('max_value', 15, 4)->nullable()->comment('최대값 (숫자형)');
$table->json('allowed_values')->nullable()->comment('허용값 목록 (선택형)');
$table->string('unit', 20)->nullable()->comment('단위 (mm, kg, 개 등)');
$table->text('description')->nullable()->comment('매개변수 설명');
$table->integer('display_order')->default(0)->comment('표시 순서');
$table->boolean('is_active')->default(true)->comment('활성 여부');
// 공통 컬럼
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'model_id']);
$table->index(['tenant_id', 'model_id', 'display_order']);
$table->unique(['tenant_id', 'model_id', 'parameter_name'], 'unique_parameter_per_model');
// 외래키 (설계용)
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->foreign('model_id')->references('id')->on('models')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('model_parameters');
}
};

View File

@@ -1,53 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('model_formulas', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('model_id')->comment('모델 ID');
$table->string('formula_name', 50)->comment('공식명 (W1, H1, area, weight 등)');
$table->text('formula_expression')->comment('공식 표현식 (W0 + 100, W0 * H0 / 1000000 등)');
$table->enum('result_type', ['INTEGER', 'DECIMAL'])->default('DECIMAL')->comment('결과 타입');
$table->integer('decimal_places')->default(2)->comment('소수점 자릿수');
$table->string('unit', 20)->nullable()->comment('결과 단위 (mm, kg, m² 등)');
$table->text('description')->nullable()->comment('공식 설명');
$table->integer('calculation_order')->default(0)->comment('계산 순서 (의존성 관리)');
$table->json('dependencies')->nullable()->comment('의존 변수 목록');
$table->boolean('is_active')->default(true)->comment('활성 여부');
// 공통 컬럼
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'model_id']);
$table->index(['tenant_id', 'model_id', 'calculation_order']);
$table->unique(['tenant_id', 'model_id', 'formula_name'], 'unique_formula_per_model');
// 외래키 (설계용)
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->foreign('model_id')->references('id')->on('models')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('model_formulas');
}
};

View File

@@ -1,57 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('bom_condition_rules', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('model_id')->comment('모델 ID');
$table->string('rule_name', 100)->comment('규칙명');
$table->text('condition_expression')->comment('조건 표현식 (screen_type == "SCREEN", W0 > 1000 등)');
$table->enum('action_type', ['INCLUDE_PRODUCT', 'EXCLUDE_PRODUCT', 'SET_QUANTITY', 'MODIFY_QUANTITY'])->comment('액션 타입');
$table->unsignedBigInteger('target_product_id')->nullable()->comment('대상 제품 ID');
$table->unsignedBigInteger('target_group_id')->nullable()->comment('대상 그룹 ID');
$table->text('quantity_formula')->nullable()->comment('수량 공식 (ceiling(W0/1000), 2 * count 등)');
$table->decimal('fixed_quantity', 15, 4)->nullable()->comment('고정 수량');
$table->text('description')->nullable()->comment('규칙 설명');
$table->integer('priority')->default(100)->comment('실행 우선순위 (낮을수록 먼저 실행)');
$table->boolean('is_active')->default(true)->comment('활성 여부');
// 공통 컬럼
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'model_id']);
$table->index(['tenant_id', 'model_id', 'priority']);
$table->index(['tenant_id', 'target_product_id']);
$table->index(['tenant_id', 'target_group_id']);
// 외래키 (설계용)
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->foreign('model_id')->references('id')->on('models')->onDelete('cascade');
$table->foreign('target_product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('target_group_id')->references('id')->on('bom_template_groups')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('bom_condition_rules');
}
};

View File

@@ -1,52 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('product_parameters', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('model_id')->comment('모델 ID');
$table->unsignedBigInteger('model_version_id')->nullable()->comment('모델 버전 ID');
$table->string('product_code', 100)->nullable()->comment('제품 코드 (참조용)');
$table->json('parameter_values')->comment('매개변수 값들 {W0: 1200, H0: 800, screen_type: "SCREEN"}');
$table->text('notes')->nullable()->comment('비고');
$table->enum('status', ['DRAFT', 'CALCULATED', 'APPROVED'])->default('DRAFT')->comment('상태');
// 공통 컬럼
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'model_id']);
$table->index(['tenant_id', 'model_id', 'model_version_id']);
$table->index(['tenant_id', 'product_code']);
$table->index(['tenant_id', 'status']);
$table->index(['created_at']);
// 외래키 (설계용)
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->foreign('model_id')->references('id')->on('models')->onDelete('cascade');
$table->foreign('model_version_id')->references('id')->on('model_versions')->onDelete('set null');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('product_parameters');
}
};

View File

@@ -1,54 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('product_calculated_values', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->unsignedBigInteger('product_parameter_id')->comment('제품 매개변수 ID');
$table->json('calculated_values')->comment('계산된 값들 {W1: 1300, H1: 900, area: 1.17, weight: 45.2}');
$table->json('bom_snapshot')->nullable()->comment('계산된 BOM 결과 스냅샷');
$table->decimal('total_cost', 15, 4)->nullable()->comment('총 비용');
$table->decimal('total_weight', 15, 4)->nullable()->comment('총 중량');
$table->integer('total_items')->nullable()->comment('총 아이템 수');
$table->timestamp('calculation_date')->useCurrent()->comment('계산 일시');
$table->boolean('is_valid')->default(true)->comment('계산 유효성 여부');
$table->text('calculation_errors')->nullable()->comment('계산 오류 메시지');
$table->string('calculation_version', 50)->nullable()->comment('계산 엔진 버전');
// 공통 컬럼
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
$table->timestamps();
$table->softDeletes();
// 인덱스
$table->index(['tenant_id', 'product_parameter_id']);
$table->index(['tenant_id', 'is_valid']);
$table->index(['calculation_date']);
$table->unique(['tenant_id', 'product_parameter_id'], 'unique_calculation_per_parameter');
// 외래키 (설계용)
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->foreign('product_parameter_id')->references('id')->on('product_parameters')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('product_calculated_values');
}
};

View File

@@ -1,52 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('bom_template_items', function (Blueprint $table) {
// group_id가 이미 존재하는지 확인 후 추가
if (!Schema::hasColumn('bom_template_items', 'group_id')) {
$table->unsignedBigInteger('group_id')->nullable()->after('bom_template_id')->comment('BOM 그룹 ID');
}
// 나머지 컬럼들 추가
if (!Schema::hasColumn('bom_template_items', 'is_conditional')) {
$table->boolean('is_conditional')->default(false)->after('qty')->comment('조건부 아이템 여부');
}
if (!Schema::hasColumn('bom_template_items', 'condition_expression')) {
$table->text('condition_expression')->nullable()->after('is_conditional')->comment('조건 표현식');
}
if (!Schema::hasColumn('bom_template_items', 'quantity_formula')) {
$table->text('quantity_formula')->nullable()->after('condition_expression')->comment('수량 계산 공식');
}
});
// 인덱스와 외래키는 별도로 처리
Schema::table('bom_template_items', function (Blueprint $table) {
if (!Schema::hasIndex('bom_template_items', ['tenant_id', 'group_id'])) {
$table->index(['tenant_id', 'group_id']);
}
// 외래키 제약조건은 생산 환경에서 제거 (SAM 규칙)
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('bom_template_items', function (Blueprint $table) {
$table->dropForeign(['group_id']);
$table->dropIndex(['tenant_id', 'group_id']);
$table->dropColumn(['group_id', 'is_conditional', 'condition_expression', 'quantity_formula']);
});
}
};

View File

@@ -1,40 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->boolean('is_parametric')->default(false)->after('product_type')->comment('매개변수 기반 제품 여부');
$table->unsignedBigInteger('base_model_id')->nullable()->after('is_parametric')->comment('기반 모델 ID (매개변수 제품용)');
$table->json('parameter_values')->nullable()->after('base_model_id')->comment('매개변수 값들 (매개변수 제품용)');
$table->json('calculated_values')->nullable()->after('parameter_values')->comment('계산된 값들 (매개변수 제품용)');
// 인덱스 추가
$table->index(['tenant_id', 'is_parametric']);
$table->index(['tenant_id', 'base_model_id']);
// 외래키 제약조건은 생산 환경에서 제거 (SAM 규칙)
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropForeign(['base_model_id']);
$table->dropIndex(['tenant_id', 'is_parametric']);
$table->dropIndex(['tenant_id', 'base_model_id']);
$table->dropColumn(['is_parametric', 'base_model_id', 'parameter_values', 'calculated_values']);
});
}
};

View File

@@ -1,573 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Category;
use App\Models\Product;
use App\Models\Material;
use App\Models\Design\DesignModel;
use App\Models\Design\ModelVersion;
use App\Models\Design\ModelParameter;
use App\Models\Design\ModelFormula;
use App\Models\Design\BomConditionRule;
use App\Models\Design\BomTemplate;
use App\Models\Design\BomTemplateItem;
use Illuminate\Support\Facades\Hash;
class KSS01ModelSeeder extends Seeder
{
/**
* Run the database seeds for KSS01 specific model.
* This creates the exact KSS01 screen door model as referenced in the codebase.
*/
public function run(): void
{
// Get or create test tenant
$tenant = Tenant::firstOrCreate(
['code' => 'KSS_DEMO'],
[
'name' => 'KSS Demo Tenant',
'description' => 'Demonstration tenant for KSS01 model',
'is_active' => true
]
);
// Get or create test user
$user = User::firstOrCreate(
['email' => 'demo@kss01.com'],
[
'name' => 'KSS01 Demo User',
'password' => Hash::make('kss01demo'),
'email_verified_at' => now()
]
);
// Associate user with tenant
if (!$user->tenants()->where('tenant_id', $tenant->id)->exists()) {
$user->tenants()->attach($tenant->id, [
'is_active' => true,
'is_default' => true
]);
}
// Create screen door category
$category = Category::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'SCREEN_DOORS'
],
[
'name' => 'Screen Doors',
'description' => 'Automatic screen door systems',
'is_active' => true
]
);
// Create KSS01 specific materials and products
$this->createKSS01Materials($tenant);
$this->createKSS01Products($tenant);
// Create the KSS01 model
$kss01Model = DesignModel::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'KSS01'
],
[
'name' => 'KSS01 Screen Door System',
'category_id' => $category->id,
'lifecycle' => 'ACTIVE',
'description' => 'Production KSS01 automatic screen door system with parametric BOM',
'is_active' => true
]
);
// Create KSS01 parameters (matching the codebase expectations)
$this->createKSS01Parameters($tenant, $kss01Model);
// Create KSS01 formulas (matching the service expectations)
$this->createKSS01Formulas($tenant, $kss01Model);
// Create KSS01 condition rules
$this->createKSS01ConditionRules($tenant, $kss01Model);
// Create BOM template
$this->createKSS01BomTemplate($tenant, $kss01Model);
$this->command->info('KSS01 model seeded successfully!');
$this->command->info('Demo tenant: ' . $tenant->code);
$this->command->info('Demo user: ' . $user->email . ' / password: kss01demo');
}
/**
* Create KSS01 specific materials
*/
private function createKSS01Materials(Tenant $tenant): void
{
$materials = [
[
'code' => 'FABRIC_KSS01',
'name' => 'KSS01 Fabric Screen',
'description' => 'Specialized fabric for KSS01 screen door',
'unit' => 'M2',
'density' => 0.35,
'color' => 'CHARCOAL'
],
[
'code' => 'STEEL_KSS01',
'name' => 'KSS01 Steel Mesh',
'description' => 'Security steel mesh for KSS01',
'unit' => 'M2',
'density' => 2.8,
'color' => 'GRAPHITE'
],
[
'code' => 'RAIL_KSS01_GUIDE',
'name' => 'KSS01 Guide Rail',
'description' => 'Precision guide rail for KSS01 system',
'unit' => 'M',
'density' => 1.2,
'color' => 'ANODIZED'
],
[
'code' => 'CABLE_KSS01_LIFT',
'name' => 'KSS01 Lift Cable',
'description' => 'High-strength lift cable for KSS01',
'unit' => 'M',
'density' => 0.15,
'color' => 'STAINLESS'
],
[
'code' => 'SEAL_KSS01_WEATHER',
'name' => 'KSS01 Weather Seal',
'description' => 'Weather sealing strip for KSS01',
'unit' => 'M',
'density' => 0.08,
'color' => 'BLACK'
]
];
foreach ($materials as $materialData) {
Material::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => $materialData['code']
],
array_merge($materialData, [
'is_active' => true,
'created_by' => 1
])
);
}
}
/**
* Create KSS01 specific products
*/
private function createKSS01Products(Tenant $tenant): void
{
$products = [
[
'code' => 'BRACKET_KSS01_WALL',
'name' => 'KSS01 Wall Bracket',
'description' => 'Heavy-duty wall mounting bracket for KSS01',
'unit' => 'EA',
'weight' => 0.8,
'color' => 'POWDER_COATED'
],
[
'code' => 'BRACKET_KSS01_CEILING',
'name' => 'KSS01 Ceiling Bracket',
'description' => 'Ceiling mounting bracket for KSS01',
'unit' => 'EA',
'weight' => 1.0,
'color' => 'POWDER_COATED'
],
[
'code' => 'MOTOR_KSS01_STD',
'name' => 'KSS01 Standard Motor',
'description' => 'Standard 12V motor for KSS01 (up to 4m²)',
'unit' => 'EA',
'weight' => 2.8,
'color' => 'BLACK'
],
[
'code' => 'MOTOR_KSS01_HEAVY',
'name' => 'KSS01 Heavy Duty Motor',
'description' => 'Heavy duty 24V motor for large KSS01 systems',
'unit' => 'EA',
'weight' => 4.2,
'color' => 'BLACK'
],
[
'code' => 'CONTROLLER_KSS01',
'name' => 'KSS01 Smart Controller',
'description' => 'Smart controller with app connectivity for KSS01',
'unit' => 'EA',
'weight' => 0.25,
'color' => 'WHITE'
],
[
'code' => 'CASE_KSS01_HEAD',
'name' => 'KSS01 Head Case',
'description' => 'Aluminum head case housing for KSS01',
'unit' => 'EA',
'weight' => 2.5,
'color' => 'ANODIZED'
],
[
'code' => 'BOTTOM_BAR_KSS01',
'name' => 'KSS01 Bottom Bar',
'description' => 'Weighted bottom bar for KSS01 screen',
'unit' => 'EA',
'weight' => 1.5,
'color' => 'ANODIZED'
],
[
'code' => 'PIPE_KSS01_ROLLER',
'name' => 'KSS01 Roller Pipe',
'description' => 'Precision roller pipe for KSS01 screen',
'unit' => 'EA',
'weight' => 1.0,
'color' => 'ANODIZED'
]
];
foreach ($products as $productData) {
Product::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => $productData['code']
],
array_merge($productData, [
'is_active' => true,
'created_by' => 1
])
);
}
}
/**
* Create KSS01 parameters (matching BomResolverService expectations)
*/
private function createKSS01Parameters(Tenant $tenant, DesignModel $model): void
{
$parameters = [
[
'parameter_name' => 'W0',
'parameter_type' => 'NUMBER',
'is_required' => true,
'default_value' => '800',
'min_value' => 600,
'max_value' => 3000,
'unit' => 'mm',
'description' => 'Opening width (clear opening)',
'sort_order' => 1
],
[
'parameter_name' => 'H0',
'parameter_type' => 'NUMBER',
'is_required' => true,
'default_value' => '600',
'min_value' => 400,
'max_value' => 2500,
'unit' => 'mm',
'description' => 'Opening height (clear opening)',
'sort_order' => 2
],
[
'parameter_name' => 'screen_type',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'FABRIC',
'options' => ['FABRIC', 'STEEL'],
'description' => 'Screen material type',
'sort_order' => 3
],
[
'parameter_name' => 'install_type',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'WALL',
'options' => ['WALL', 'CEILING', 'RECESSED'],
'description' => 'Installation method',
'sort_order' => 4
],
[
'parameter_name' => 'power_source',
'parameter_type' => 'SELECT',
'is_required' => false,
'default_value' => 'AC',
'options' => ['AC', 'DC', 'BATTERY'],
'description' => 'Power source type',
'sort_order' => 5
]
];
foreach ($parameters as $paramData) {
ModelParameter::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'parameter_name' => $paramData['parameter_name']
],
$paramData
);
}
}
/**
* Create KSS01 formulas (matching BomResolverService::resolveKSS01)
*/
private function createKSS01Formulas(Tenant $tenant, DesignModel $model): void
{
$formulas = [
[
'formula_name' => 'W1',
'expression' => 'W0 + 100',
'description' => 'Overall width (opening + frame)',
'sort_order' => 1
],
[
'formula_name' => 'H1',
'expression' => 'H0 + 100',
'description' => 'Overall height (opening + frame)',
'sort_order' => 2
],
[
'formula_name' => 'area',
'expression' => '(W1 * H1) / 1000000',
'description' => 'Total area in square meters',
'sort_order' => 3
],
[
'formula_name' => 'weight',
'expression' => 'area * 8 + W1 / 1000 * 2.5',
'description' => 'Estimated total weight in kg',
'sort_order' => 4
],
[
'formula_name' => 'motor_load',
'expression' => 'weight * 1.5 + area * 3',
'description' => 'Motor load calculation',
'sort_order' => 5
],
[
'formula_name' => 'bracket_count',
'expression' => 'if(W1 > 1000, 3, 2)',
'description' => 'Number of brackets required',
'sort_order' => 6
],
[
'formula_name' => 'rail_length',
'expression' => '(W1 + H1) * 2 / 1000',
'description' => 'Guide rail length in meters',
'sort_order' => 7
]
];
foreach ($formulas as $formulaData) {
ModelFormula::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'formula_name' => $formulaData['formula_name']
],
$formulaData
);
}
}
/**
* Create KSS01 condition rules
*/
private function createKSS01ConditionRules(Tenant $tenant, DesignModel $model): void
{
$rules = [
// Screen material selection
[
'rule_name' => 'Fabric Screen Material',
'condition_expression' => 'screen_type == "FABRIC"',
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'FABRIC_KSS01')->first()->id,
'quantity_multiplier' => 'area',
'description' => 'Include fabric screen material',
'sort_order' => 1
],
[
'rule_name' => 'Steel Screen Material',
'condition_expression' => 'screen_type == "STEEL"',
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'STEEL_KSS01')->first()->id,
'quantity_multiplier' => 'area',
'description' => 'Include steel mesh material',
'sort_order' => 2
],
// Motor selection based on load
[
'rule_name' => 'Standard Motor',
'condition_expression' => 'motor_load <= 20',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'MOTOR_KSS01_STD')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Standard motor for normal loads',
'sort_order' => 3
],
[
'rule_name' => 'Heavy Duty Motor',
'condition_expression' => 'motor_load > 20',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'MOTOR_KSS01_HEAVY')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Heavy duty motor for high loads',
'sort_order' => 4
],
// Bracket selection based on installation
[
'rule_name' => 'Wall Brackets',
'condition_expression' => 'install_type == "WALL"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'BRACKET_KSS01_WALL')->first()->id,
'quantity_multiplier' => 'bracket_count',
'description' => 'Wall mounting brackets',
'sort_order' => 5
],
[
'rule_name' => 'Ceiling Brackets',
'condition_expression' => 'install_type == "CEILING"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'BRACKET_KSS01_CEILING')->first()->id,
'quantity_multiplier' => 'bracket_count',
'description' => 'Ceiling mounting brackets',
'sort_order' => 6
],
// Guide rail
[
'rule_name' => 'Guide Rail',
'condition_expression' => 'true', // Always include
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'RAIL_KSS01_GUIDE')->first()->id,
'quantity_multiplier' => 'rail_length',
'description' => 'Guide rail for screen movement',
'sort_order' => 7
],
// Weather sealing for large openings
[
'rule_name' => 'Weather Seal for Large Openings',
'condition_expression' => 'area > 3.0',
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'SEAL_KSS01_WEATHER')->first()->id,
'quantity_multiplier' => 'rail_length',
'description' => 'Weather sealing for large openings',
'sort_order' => 8
]
];
foreach ($rules as $ruleData) {
BomConditionRule::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'rule_name' => $ruleData['rule_name']
],
$ruleData
);
}
}
/**
* Create KSS01 BOM template
*/
private function createKSS01BomTemplate(Tenant $tenant, DesignModel $model): void
{
// Create model version
$modelVersion = ModelVersion::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'version_no' => '1.0'
],
[
'status' => 'RELEASED',
'description' => 'Initial release of KSS01',
'created_by' => 1
]
);
// Create BOM template
$bomTemplate = BomTemplate::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_version_id' => $modelVersion->id,
'name' => 'KSS01 Base BOM'
],
[
'description' => 'Base BOM template for KSS01 screen door system',
'is_active' => true,
'created_by' => 1
]
);
// Create base BOM items (always included components)
$baseItems = [
[
'ref_type' => 'PRODUCT',
'ref_id' => Product::where('tenant_id', $tenant->id)->where('code', 'CONTROLLER_KSS01')->first()->id,
'quantity' => 1,
'waste_rate' => 0,
'order' => 1
],
[
'ref_type' => 'PRODUCT',
'ref_id' => Product::where('tenant_id', $tenant->id)->where('code', 'CASE_KSS01_HEAD')->first()->id,
'quantity' => 1,
'waste_rate' => 0,
'order' => 2
],
[
'ref_type' => 'PRODUCT',
'ref_id' => Product::where('tenant_id', $tenant->id)->where('code', 'BOTTOM_BAR_KSS01')->first()->id,
'quantity' => 1,
'waste_rate' => 0,
'order' => 3
],
[
'ref_type' => 'PRODUCT',
'ref_id' => Product::where('tenant_id', $tenant->id)->where('code', 'PIPE_KSS01_ROLLER')->first()->id,
'quantity' => 1,
'waste_rate' => 0,
'order' => 4
],
[
'ref_type' => 'MATERIAL',
'ref_id' => Material::where('tenant_id', $tenant->id)->where('code', 'CABLE_KSS01_LIFT')->first()->id,
'quantity' => 2, // Two cables
'waste_rate' => 10,
'order' => 5
]
];
foreach ($baseItems as $itemData) {
BomTemplateItem::firstOrCreate(
[
'bom_template_id' => $bomTemplate->id,
'ref_type' => $itemData['ref_type'],
'ref_id' => $itemData['ref_id'],
'order' => $itemData['order']
],
$itemData
);
}
}
}

View File

@@ -1,158 +0,0 @@
<?php
namespace Database\Seeders;
use App\Models\BomConditionRule;
use App\Models\Model;
use App\Models\ModelFormula;
use App\Models\ModelParameter;
use Illuminate\Database\Seeder;
class ParameterBasedBomTestSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Create KSS01 (Screen) Model with complete parameter-based BOM setup
$kss01 = Model::factory()
->screen()
->released()
->create([
'code' => 'KSS01',
'name' => '스크린 블라인드 표준형',
'description' => '매개변수 기반 스크린 블라인드',
'product_family' => 'SCREEN',
]);
// Create parameters for KSS01
ModelParameter::factory()
->screenParameters()
->create(['model_id' => $kss01->id]);
// Create formulas for KSS01
ModelFormula::factory()
->screenFormulas()
->create(['model_id' => $kss01->id]);
// Create condition rules for KSS01
BomConditionRule::factory()
->screenRules()
->create(['model_id' => $kss01->id]);
// Create KST01 (Steel) Model
$kst01 = Model::factory()
->steel()
->released()
->create([
'code' => 'KST01',
'name' => '스틸 도어 표준형',
'description' => '매개변수 기반 스틸 도어',
'product_family' => 'STEEL',
]);
// Create parameters for KST01
ModelParameter::factory()
->steelParameters()
->create(['model_id' => $kst01->id]);
// Create formulas for KST01
ModelFormula::factory()
->steelFormulas()
->create(['model_id' => $kst01->id]);
// Create condition rules for KST01
BomConditionRule::factory()
->steelRules()
->create(['model_id' => $kst01->id]);
// Create additional test models for edge cases
$testModels = [
// Draft model for testing incomplete states
[
'code' => 'TEST-DRAFT',
'name' => '테스트 드래프트 모델',
'status' => 'DRAFT',
'product_family' => 'SCREEN',
],
// Inactive model for testing filtering
[
'code' => 'TEST-INACTIVE',
'name' => '테스트 비활성 모델',
'status' => 'RELEASED',
'product_family' => 'SCREEN',
'is_active' => false,
],
// Complex model for performance testing
[
'code' => 'COMPLEX-01',
'name' => '복합 테스트 모델',
'status' => 'RELEASED',
'product_family' => 'SCREEN',
],
];
foreach ($testModels as $modelData) {
$model = Model::factory()->create($modelData);
// Add basic parameters
ModelParameter::factory()
->count(3)
->number()
->create(['model_id' => $model->id]);
// Add basic formulas
ModelFormula::factory()
->count(2)
->number()
->create(['model_id' => $model->id]);
// Add condition rules
BomConditionRule::factory()
->count(2)
->create(['model_id' => $model->id]);
}
// Create models for multi-tenant testing
$tenant2Models = Model::factory()
->count(3)
->screen()
->create(['tenant_id' => 2]);
foreach ($tenant2Models as $model) {
ModelParameter::factory()
->count(2)
->create(['model_id' => $model->id, 'tenant_id' => 2]);
ModelFormula::factory()
->count(1)
->create(['model_id' => $model->id, 'tenant_id' => 2]);
}
// Create performance test data (large dataset)
if (app()->environment('testing')) {
$performanceModel = Model::factory()
->screen()
->create([
'code' => 'PERF-TEST',
'name' => '성능 테스트 모델',
]);
// Large number of parameters for performance testing
ModelParameter::factory()
->count(50)
->create(['model_id' => $performanceModel->id]);
// Large number of formulas
ModelFormula::factory()
->count(30)
->create(['model_id' => $performanceModel->id]);
// Large number of condition rules
BomConditionRule::factory()
->count(100)
->create(['model_id' => $performanceModel->id]);
}
}
}

View File

@@ -1,820 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Category;
use App\Models\Product;
use App\Models\Material;
use App\Models\Design\DesignModel;
use App\Models\Design\ModelVersion;
use App\Models\Design\ModelParameter;
use App\Models\Design\ModelFormula;
use App\Models\Design\BomConditionRule;
use App\Models\Design\BomTemplate;
use App\Models\Design\BomTemplateItem;
use Illuminate\Support\Facades\Hash;
class ParametricBomSeeder extends Seeder
{
/**
* Run the database seeds for parametric BOM testing.
*/
public function run(): void
{
// Create test tenant
$tenant = Tenant::firstOrCreate(
['code' => 'TEST_TENANT'],
[
'name' => 'Test Tenant for Parametric BOM',
'description' => 'Tenant for testing parametric BOM system',
'is_active' => true
]
);
// Create test user
$user = User::firstOrCreate(
['email' => 'test@parametric-bom.com'],
[
'name' => 'Parametric BOM Test User',
'password' => Hash::make('password'),
'email_verified_at' => now()
]
);
// Associate user with tenant
if (!$user->tenants()->where('tenant_id', $tenant->id)->exists()) {
$user->tenants()->attach($tenant->id, [
'is_active' => true,
'is_default' => true
]);
}
// Create categories
$screenCategory = Category::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'SCREEN_SYSTEMS'
],
[
'name' => 'Screen Door Systems',
'description' => 'Automatic screen door systems',
'is_active' => true
]
);
$materialsCategory = Category::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'MATERIALS'
],
[
'name' => 'Raw Materials',
'description' => 'Raw materials and components',
'is_active' => true
]
);
$componentsCategory = Category::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'COMPONENTS'
],
[
'name' => 'Components',
'description' => 'Manufactured components and parts',
'is_active' => true
]
);
// Create test products
$this->createTestProducts($tenant, $componentsCategory);
// Create test materials
$this->createTestMaterials($tenant, $materialsCategory);
// Create design models
$this->createDesignModels($tenant, $screenCategory);
$this->command->info('Parametric BOM test data seeded successfully!');
}
/**
* Create test products
*/
private function createTestProducts(Tenant $tenant, Category $category): void
{
$products = [
[
'code' => 'BRACKET_WALL_STD',
'name' => 'Standard Wall Bracket',
'description' => 'Standard mounting bracket for wall installation',
'unit' => 'EA',
'weight' => 0.5,
'color' => 'WHITE'
],
[
'code' => 'BRACKET_CEILING_STD',
'name' => 'Standard Ceiling Bracket',
'description' => 'Standard mounting bracket for ceiling installation',
'unit' => 'EA',
'weight' => 0.7,
'color' => 'WHITE'
],
[
'code' => 'MOTOR_DC_12V',
'name' => 'DC Motor 12V',
'description' => 'DC motor for screen operation, 12V',
'unit' => 'EA',
'weight' => 2.5,
'color' => 'BLACK'
],
[
'code' => 'MOTOR_DC_24V',
'name' => 'DC Motor 24V',
'description' => 'DC motor for screen operation, 24V (for larger screens)',
'unit' => 'EA',
'weight' => 3.2,
'color' => 'BLACK'
],
[
'code' => 'CONTROLLER_BASIC',
'name' => 'Basic Controller',
'description' => 'Basic remote controller for screen operation',
'unit' => 'EA',
'weight' => 0.2,
'color' => 'WHITE'
],
[
'code' => 'CONTROLLER_SMART',
'name' => 'Smart Controller',
'description' => 'Smart controller with app connectivity',
'unit' => 'EA',
'weight' => 0.3,
'color' => 'BLACK'
],
[
'code' => 'CASE_ALUMINUM',
'name' => 'Aluminum Case',
'description' => 'Aluminum housing case for screen mechanism',
'unit' => 'EA',
'weight' => 1.8,
'color' => 'SILVER'
],
[
'code' => 'CASE_PLASTIC',
'name' => 'Plastic Case',
'description' => 'Plastic housing case for screen mechanism',
'unit' => 'EA',
'weight' => 1.2,
'color' => 'WHITE'
]
];
foreach ($products as $productData) {
Product::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => $productData['code']
],
array_merge($productData, [
'category_id' => $category->id,
'is_active' => true,
'created_by' => 1
])
);
}
}
/**
* Create test materials
*/
private function createTestMaterials(Tenant $tenant, Category $category): void
{
$materials = [
[
'code' => 'FABRIC_SCREEN_STD',
'name' => 'Standard Screen Fabric',
'description' => 'Standard fabric material for screen doors',
'unit' => 'M2',
'density' => 0.3, // kg/m2
'color' => 'GRAY'
],
[
'code' => 'FABRIC_SCREEN_PREMIUM',
'name' => 'Premium Screen Fabric',
'description' => 'Premium fabric material with enhanced durability',
'unit' => 'M2',
'density' => 0.4,
'color' => 'CHARCOAL'
],
[
'code' => 'STEEL_MESH_FINE',
'name' => 'Fine Steel Mesh',
'description' => 'Fine steel mesh for security screens',
'unit' => 'M2',
'density' => 2.5,
'color' => 'SILVER'
],
[
'code' => 'STEEL_MESH_COARSE',
'name' => 'Coarse Steel Mesh',
'description' => 'Coarse steel mesh for ventilation screens',
'unit' => 'M2',
'density' => 2.0,
'color' => 'SILVER'
],
[
'code' => 'RAIL_GUIDE_ALU',
'name' => 'Aluminum Guide Rail',
'description' => 'Aluminum guide rail for screen movement',
'unit' => 'M',
'density' => 0.8, // kg/m
'color' => 'SILVER'
],
[
'code' => 'RAIL_GUIDE_PLASTIC',
'name' => 'Plastic Guide Rail',
'description' => 'Plastic guide rail for lightweight screens',
'unit' => 'M',
'density' => 0.3,
'color' => 'WHITE'
],
[
'code' => 'CABLE_STEEL',
'name' => 'Steel Cable',
'description' => 'Steel cable for screen lifting mechanism',
'unit' => 'M',
'density' => 0.1,
'color' => 'SILVER'
],
[
'code' => 'SPRING_TENSION',
'name' => 'Tension Spring',
'description' => 'Spring for screen tension adjustment',
'unit' => 'EA',
'weight' => 0.05,
'color' => 'SILVER'
]
];
foreach ($materials as $materialData) {
Material::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => $materialData['code']
],
array_merge($materialData, [
'category_id' => $category->id,
'is_active' => true,
'created_by' => 1
])
);
}
}
/**
* Create design models with parameters, formulas, and rules
*/
private function createDesignModels(Tenant $tenant, Category $category): void
{
// Create basic screen door model
$basicModel = DesignModel::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'BSD01'
],
[
'name' => 'Basic Screen Door',
'category_id' => $category->id,
'lifecycle' => 'ACTIVE',
'description' => 'Basic parametric screen door model for testing',
'is_active' => true
]
);
$this->createModelParameters($tenant, $basicModel);
$this->createModelFormulas($tenant, $basicModel);
$this->createBomConditionRules($tenant, $basicModel);
$this->createBomTemplate($tenant, $basicModel);
// Create advanced screen door model
$advancedModel = DesignModel::firstOrCreate(
[
'tenant_id' => $tenant->id,
'code' => 'ASD01'
],
[
'name' => 'Advanced Screen Door',
'category_id' => $category->id,
'lifecycle' => 'ACTIVE',
'description' => 'Advanced parametric screen door model with more features',
'is_active' => true
]
);
$this->createAdvancedModelParameters($tenant, $advancedModel);
$this->createAdvancedModelFormulas($tenant, $advancedModel);
$this->createAdvancedBomConditionRules($tenant, $advancedModel);
}
/**
* Create basic model parameters
*/
private function createModelParameters(Tenant $tenant, DesignModel $model): void
{
$parameters = [
[
'parameter_name' => 'width',
'parameter_type' => 'NUMBER',
'is_required' => true,
'default_value' => '1000',
'min_value' => 500,
'max_value' => 3000,
'unit' => 'mm',
'description' => 'Screen width',
'sort_order' => 1
],
[
'parameter_name' => 'height',
'parameter_type' => 'NUMBER',
'is_required' => true,
'default_value' => '2000',
'min_value' => 1000,
'max_value' => 3000,
'unit' => 'mm',
'description' => 'Screen height',
'sort_order' => 2
],
[
'parameter_name' => 'screen_type',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'FABRIC',
'options' => ['FABRIC', 'STEEL_FINE', 'STEEL_COARSE'],
'description' => 'Type of screen material',
'sort_order' => 3
],
[
'parameter_name' => 'installation_type',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'WALL',
'options' => ['WALL', 'CEILING', 'RECESSED'],
'description' => 'Installation method',
'sort_order' => 4
],
[
'parameter_name' => 'motor_power',
'parameter_type' => 'SELECT',
'is_required' => false,
'default_value' => 'AUTO',
'options' => ['AUTO', '12V', '24V'],
'description' => 'Motor power selection (AUTO = based on size)',
'sort_order' => 5
]
];
foreach ($parameters as $paramData) {
ModelParameter::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'parameter_name' => $paramData['parameter_name']
],
$paramData
);
}
}
/**
* Create basic model formulas
*/
private function createModelFormulas(Tenant $tenant, DesignModel $model): void
{
$formulas = [
[
'formula_name' => 'outer_width',
'expression' => 'width + 100',
'description' => 'Outer width including frame',
'sort_order' => 1
],
[
'formula_name' => 'outer_height',
'expression' => 'height + 150',
'description' => 'Outer height including frame',
'sort_order' => 2
],
[
'formula_name' => 'screen_area',
'expression' => '(width * height) / 1000000',
'description' => 'Screen area in square meters',
'sort_order' => 3
],
[
'formula_name' => 'frame_perimeter',
'expression' => '(outer_width + outer_height) * 2 / 1000',
'description' => 'Frame perimeter in meters',
'sort_order' => 4
],
[
'formula_name' => 'total_weight',
'expression' => 'screen_area * 0.5 + frame_perimeter * 0.8',
'description' => 'Estimated total weight in kg',
'sort_order' => 5
]
];
foreach ($formulas as $formulaData) {
ModelFormula::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'formula_name' => $formulaData['formula_name']
],
$formulaData
);
}
}
/**
* Create BOM condition rules for basic model
*/
private function createBomConditionRules(Tenant $tenant, DesignModel $model): void
{
$rules = [
// Motor selection based on size
[
'rule_name' => 'Large Screen Motor',
'condition_expression' => 'screen_area > 4.0 OR motor_power == "24V"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'MOTOR_DC_24V')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Use 24V motor for large screens',
'sort_order' => 1
],
[
'rule_name' => 'Standard Screen Motor',
'condition_expression' => 'screen_area <= 4.0 AND motor_power != "24V"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'MOTOR_DC_12V')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Use 12V motor for standard screens',
'sort_order' => 2
],
// Screen material selection
[
'rule_name' => 'Fabric Screen Material',
'condition_expression' => 'screen_type == "FABRIC"',
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'FABRIC_SCREEN_STD')->first()->id,
'quantity_multiplier' => 1.1, // 10% waste
'description' => 'Standard fabric screen material',
'sort_order' => 3
],
[
'rule_name' => 'Fine Steel Screen Material',
'condition_expression' => 'screen_type == "STEEL_FINE"',
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'STEEL_MESH_FINE')->first()->id,
'quantity_multiplier' => 1.05, // 5% waste
'description' => 'Fine steel mesh material',
'sort_order' => 4
],
// Bracket selection based on installation
[
'rule_name' => 'Wall Brackets',
'condition_expression' => 'installation_type == "WALL"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'BRACKET_WALL_STD')->first()->id,
'quantity_multiplier' => 2, // Always 2 brackets
'description' => 'Wall mounting brackets',
'sort_order' => 5
],
[
'rule_name' => 'Ceiling Brackets',
'condition_expression' => 'installation_type == "CEILING"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'BRACKET_CEILING_STD')->first()->id,
'quantity_multiplier' => 2,
'description' => 'Ceiling mounting brackets',
'sort_order' => 6
],
// Extra brackets for wide screens
[
'rule_name' => 'Extra Brackets for Wide Screens',
'condition_expression' => 'width > 2000',
'action_type' => 'MODIFY_QUANTITY',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'BRACKET_WALL_STD')->first()->id,
'quantity_multiplier' => 1.5, // Add 50% more brackets
'description' => 'Additional brackets for wide screens',
'sort_order' => 7
]
];
foreach ($rules as $ruleData) {
BomConditionRule::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'rule_name' => $ruleData['rule_name']
],
$ruleData
);
}
}
/**
* Create BOM template for basic model
*/
private function createBomTemplate(Tenant $tenant, DesignModel $model): void
{
// Create model version first
$modelVersion = ModelVersion::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'version_no' => '1.0'
],
[
'status' => 'RELEASED',
'description' => 'Initial release version',
'created_by' => 1
]
);
// Create BOM template
$bomTemplate = BomTemplate::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_version_id' => $modelVersion->id,
'name' => 'Basic Screen Door BOM'
],
[
'description' => 'Base BOM template for basic screen door',
'is_active' => true,
'created_by' => 1
]
);
// Create BOM template items (base components always included)
$baseItems = [
[
'ref_type' => 'MATERIAL',
'ref_id' => Material::where('tenant_id', $tenant->id)->where('code', 'RAIL_GUIDE_ALU')->first()->id,
'quantity' => 1, // Will be calculated by formula
'waste_rate' => 5,
'order' => 1
],
[
'ref_type' => 'MATERIAL',
'ref_id' => Material::where('tenant_id', $tenant->id)->where('code', 'CABLE_STEEL')->first()->id,
'quantity' => 2, // Height * 2
'waste_rate' => 10,
'order' => 2
],
[
'ref_type' => 'MATERIAL',
'ref_id' => Material::where('tenant_id', $tenant->id)->where('code', 'SPRING_TENSION')->first()->id,
'quantity' => 2,
'waste_rate' => 0,
'order' => 3
],
[
'ref_type' => 'PRODUCT',
'ref_id' => Product::where('tenant_id', $tenant->id)->where('code', 'CONTROLLER_BASIC')->first()->id,
'quantity' => 1,
'waste_rate' => 0,
'order' => 4
],
[
'ref_type' => 'PRODUCT',
'ref_id' => Product::where('tenant_id', $tenant->id)->where('code', 'CASE_ALUMINUM')->first()->id,
'quantity' => 1,
'waste_rate' => 0,
'order' => 5
]
];
foreach ($baseItems as $itemData) {
BomTemplateItem::firstOrCreate(
[
'bom_template_id' => $bomTemplate->id,
'ref_type' => $itemData['ref_type'],
'ref_id' => $itemData['ref_id'],
'order' => $itemData['order']
],
$itemData
);
}
}
/**
* Create advanced model parameters
*/
private function createAdvancedModelParameters(Tenant $tenant, DesignModel $model): void
{
$parameters = [
[
'parameter_name' => 'width',
'parameter_type' => 'NUMBER',
'is_required' => true,
'default_value' => '1200',
'min_value' => 600,
'max_value' => 4000,
'unit' => 'mm',
'description' => 'Screen width',
'sort_order' => 1
],
[
'parameter_name' => 'height',
'parameter_type' => 'NUMBER',
'is_required' => true,
'default_value' => '2200',
'min_value' => 1200,
'max_value' => 3500,
'unit' => 'mm',
'description' => 'Screen height',
'sort_order' => 2
],
[
'parameter_name' => 'screen_type',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'FABRIC_PREMIUM',
'options' => ['FABRIC_STD', 'FABRIC_PREMIUM', 'STEEL_FINE', 'STEEL_COARSE'],
'description' => 'Type of screen material',
'sort_order' => 3
],
[
'parameter_name' => 'controller_type',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'SMART',
'options' => ['BASIC', 'SMART'],
'description' => 'Controller type',
'sort_order' => 4
],
[
'parameter_name' => 'case_material',
'parameter_type' => 'SELECT',
'is_required' => true,
'default_value' => 'ALUMINUM',
'options' => ['ALUMINUM', 'PLASTIC'],
'description' => 'Case material',
'sort_order' => 5
],
[
'parameter_name' => 'wind_resistance',
'parameter_type' => 'NUMBER',
'is_required' => false,
'default_value' => '80',
'min_value' => 40,
'max_value' => 120,
'unit' => 'km/h',
'description' => 'Required wind resistance',
'sort_order' => 6
]
];
foreach ($parameters as $paramData) {
ModelParameter::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'parameter_name' => $paramData['parameter_name']
],
$paramData
);
}
}
/**
* Create advanced model formulas
*/
private function createAdvancedModelFormulas(Tenant $tenant, DesignModel $model): void
{
$formulas = [
[
'formula_name' => 'outer_width',
'expression' => 'width + 120',
'description' => 'Outer width including reinforced frame',
'sort_order' => 1
],
[
'formula_name' => 'outer_height',
'expression' => 'height + 180',
'description' => 'Outer height including reinforced frame',
'sort_order' => 2
],
[
'formula_name' => 'screen_area',
'expression' => '(width * height) / 1000000',
'description' => 'Screen area in square meters',
'sort_order' => 3
],
[
'formula_name' => 'wind_load',
'expression' => 'screen_area * wind_resistance * 0.6',
'description' => 'Wind load calculation in Newtons',
'sort_order' => 4
],
[
'formula_name' => 'required_motor_torque',
'expression' => 'wind_load * 0.1 + screen_area * 5',
'description' => 'Required motor torque in Nm',
'sort_order' => 5
],
[
'formula_name' => 'frame_sections_count',
'expression' => 'ceiling(width / 600) + ceiling(height / 800)',
'description' => 'Number of frame sections needed',
'sort_order' => 6
]
];
foreach ($formulas as $formulaData) {
ModelFormula::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'formula_name' => $formulaData['formula_name']
],
$formulaData
);
}
}
/**
* Create advanced BOM condition rules
*/
private function createAdvancedBomConditionRules(Tenant $tenant, DesignModel $model): void
{
$rules = [
// Motor selection based on calculated torque
[
'rule_name' => 'High Torque Motor Required',
'condition_expression' => 'required_motor_torque > 15 OR wind_resistance > 100',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'MOTOR_DC_24V')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Use high-power motor for demanding conditions',
'sort_order' => 1
],
// Screen material selection
[
'rule_name' => 'Premium Fabric Material',
'condition_expression' => 'screen_type == "FABRIC_PREMIUM"',
'action_type' => 'INCLUDE',
'target_type' => 'MATERIAL',
'target_id' => Material::where('tenant_id', $tenant->id)->where('code', 'FABRIC_SCREEN_PREMIUM')->first()->id,
'quantity_multiplier' => 1.05,
'description' => 'Premium fabric screen material',
'sort_order' => 2
],
// Controller selection
[
'rule_name' => 'Smart Controller',
'condition_expression' => 'controller_type == "SMART"',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'CONTROLLER_SMART')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Smart controller with app connectivity',
'sort_order' => 3
],
// Case material selection
[
'rule_name' => 'Plastic Case for Light Duty',
'condition_expression' => 'case_material == "PLASTIC" AND wind_resistance <= 60',
'action_type' => 'INCLUDE',
'target_type' => 'PRODUCT',
'target_id' => Product::where('tenant_id', $tenant->id)->where('code', 'CASE_PLASTIC')->first()->id,
'quantity_multiplier' => 1,
'description' => 'Plastic case for light-duty applications',
'sort_order' => 4
]
];
foreach ($rules as $ruleData) {
BomConditionRule::firstOrCreate(
[
'tenant_id' => $tenant->id,
'model_id' => $model->id,
'rule_name' => $ruleData['rule_name']
],
$ruleData
);
}
}
}