feat: 시뮬레이터 동기화를 위한 DB 스키마 확장
- items 테이블에 process_type, item_category 필드 추가 - category_groups 테이블 생성 (면적/중량/수량 기반 단가 계산 분류) - CategoryGroup 모델 및 단가 계산 헬퍼 메서드 구현
This commit is contained in:
132
app/Models/CategoryGroup.php
Normal file
132
app/Models/CategoryGroup.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Tenants\Tenant;
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* 카테고리 그룹 모델
|
||||
*
|
||||
* 품목 카테고리별 단가 계산 방식(면적/중량/수량 기반) 분류
|
||||
* Design 시뮬레이터와 동기화를 위한 핵심 테이블
|
||||
*/
|
||||
class CategoryGroup extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'category_groups';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'code',
|
||||
'name',
|
||||
'multiplier_variable',
|
||||
'categories',
|
||||
'description',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'categories' => 'array',
|
||||
'sort_order' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 단가 계산 방식 상수
|
||||
*/
|
||||
public const CODE_AREA_BASED = 'area_based'; // 면적 기반 (M)
|
||||
|
||||
public const CODE_WEIGHT_BASED = 'weight_based'; // 중량 기반 (K)
|
||||
|
||||
public const CODE_QUANTITY_BASED = 'quantity_based'; // 수량 기반 (null)
|
||||
|
||||
/**
|
||||
* 곱셈 변수 상수
|
||||
*/
|
||||
public const MULTIPLIER_AREA = 'M'; // 면적 (㎡)
|
||||
|
||||
public const MULTIPLIER_WEIGHT = 'K'; // 중량 (kg)
|
||||
|
||||
/**
|
||||
* 테넌트 관계
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목 카테고리로 해당 그룹 조회
|
||||
*
|
||||
* @param string $itemCategory 품목분류 (원단, 패널, 도장 등)
|
||||
*/
|
||||
public static function findByItemCategory(int $tenantId, string $itemCategory): ?self
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('is_active', true)
|
||||
->whereJsonContains('categories', $itemCategory)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 곱셈 변수 값 계산
|
||||
*
|
||||
* @param array $variables 계산 변수 배열 ['M' => 6.099, 'K' => 25.5, ...]
|
||||
* @return float 곱셈 값 (없으면 1)
|
||||
*/
|
||||
public function getMultiplierValue(array $variables): float
|
||||
{
|
||||
if (empty($this->multiplier_variable)) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (float) ($variables[$this->multiplier_variable] ?? 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 단가 계산
|
||||
*
|
||||
* @param float $basePrice 기본 단가
|
||||
* @param array $variables 계산 변수 배열
|
||||
* @return array ['final_price' => float, 'calculation_note' => string, 'multiplier' => float]
|
||||
*/
|
||||
public function calculatePrice(float $basePrice, array $variables): array
|
||||
{
|
||||
$multiplier = $this->getMultiplierValue($variables);
|
||||
|
||||
if ($multiplier === 1.0) {
|
||||
return [
|
||||
'final_price' => $basePrice,
|
||||
'calculation_note' => '수량단가',
|
||||
'multiplier' => 1.0,
|
||||
];
|
||||
}
|
||||
|
||||
$unit = match ($this->multiplier_variable) {
|
||||
self::MULTIPLIER_AREA => '㎡',
|
||||
self::MULTIPLIER_WEIGHT => 'kg',
|
||||
default => '',
|
||||
};
|
||||
|
||||
return [
|
||||
'final_price' => $basePrice * $multiplier,
|
||||
'calculation_note' => sprintf(
|
||||
'%s (%s원/%s × %.3f%s)',
|
||||
$this->name,
|
||||
number_format($basePrice),
|
||||
$unit,
|
||||
$multiplier,
|
||||
$unit
|
||||
),
|
||||
'multiplier' => $multiplier,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* Design 시뮬레이터 동기화를 위한 items 테이블 필드 추가
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('items', function (Blueprint $table) {
|
||||
// 공정유형: screen(스크린), bending(절곡), electric(전기), steel(철재), assembly(조립)
|
||||
$table->string('process_type', 20)->nullable()->after('category_id')
|
||||
->comment('공정유형: screen, bending, electric, steel, assembly');
|
||||
|
||||
// 품목분류: 원단, 패널, 도장, 표면처리, 가이드레일, 케이스, 모터, 제어반 등
|
||||
$table->string('item_category', 50)->nullable()->after('process_type')
|
||||
->comment('품목분류: 원단, 패널, 도장, 가이드레일, 모터 등');
|
||||
|
||||
// 인덱스 추가
|
||||
$table->index('process_type', 'idx_items_process_type');
|
||||
$table->index('item_category', 'idx_items_item_category');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('items', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_items_process_type');
|
||||
$table->dropIndex('idx_items_item_category');
|
||||
$table->dropColumn(['process_type', 'item_category']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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('category_groups', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
|
||||
|
||||
// 코드: area_based, weight_based, quantity_based
|
||||
$table->string('code', 50)->comment('코드: area_based, weight_based, quantity_based');
|
||||
|
||||
// 이름: 면적기반, 중량기반, 수량기반
|
||||
$table->string('name', 100)->comment('이름: 면적기반, 중량기반, 수량기반');
|
||||
|
||||
// 곱셈 변수: M(면적), K(중량), null(수량)
|
||||
$table->string('multiplier_variable', 20)->nullable()
|
||||
->comment('곱셈 변수: M(면적), K(중량), null(수량기반)');
|
||||
|
||||
// 소속 카테고리 목록 (JSON 배열)
|
||||
$table->json('categories')->nullable()
|
||||
->comment('소속 카테고리 목록: ["원단", "패널", "도장"]');
|
||||
|
||||
$table->text('description')->nullable();
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->boolean('is_active')->default(true);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
// 인덱스
|
||||
$table->index('tenant_id', 'idx_category_groups_tenant');
|
||||
$table->unique(['tenant_id', 'code'], 'uq_category_groups_tenant_code');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('category_groups');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user