From b7182344724af67b7d0f28fa9e56ff9f4b6b5666 Mon Sep 17 00:00:00 2001 From: hskwon Date: Wed, 24 Dec 2025 14:23:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8B=9C=EB=AE=AC=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=8F=99=EA=B8=B0=ED=99=94=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20DB=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - items 테이블에 process_type, item_category 필드 추가 - category_groups 테이블 생성 (면적/중량/수량 기반 단가 계산 분류) - CategoryGroup 모델 및 단가 계산 헬퍼 메서드 구현 --- app/Models/CategoryGroup.php | 132 ++++++++++++++++++ ...01_add_simulator_fields_to_items_table.php | 42 ++++++ ...24_000002_create_category_groups_table.php | 53 +++++++ 3 files changed, 227 insertions(+) create mode 100644 app/Models/CategoryGroup.php create mode 100644 database/migrations/2025_12_24_000001_add_simulator_fields_to_items_table.php create mode 100644 database/migrations/2025_12_24_000002_create_category_groups_table.php diff --git a/app/Models/CategoryGroup.php b/app/Models/CategoryGroup.php new file mode 100644 index 00000000..b30d679e --- /dev/null +++ b/app/Models/CategoryGroup.php @@ -0,0 +1,132 @@ + '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, + ]; + } +} diff --git a/database/migrations/2025_12_24_000001_add_simulator_fields_to_items_table.php b/database/migrations/2025_12_24_000001_add_simulator_fields_to_items_table.php new file mode 100644 index 00000000..8fd234b6 --- /dev/null +++ b/database/migrations/2025_12_24_000001_add_simulator_fields_to_items_table.php @@ -0,0 +1,42 @@ +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']); + }); + } +}; diff --git a/database/migrations/2025_12_24_000002_create_category_groups_table.php b/database/migrations/2025_12_24_000002_create_category_groups_table.php new file mode 100644 index 00000000..49394d9b --- /dev/null +++ b/database/migrations/2025_12_24_000002_create_category_groups_table.php @@ -0,0 +1,53 @@ +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'); + } +};