From 6c9735581dedc2cc438196e96ce580ffb28c8cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 29 Jan 2026 19:30:46 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=AC=EC=A0=81=20=EB=8B=A8=EA=B0=80?= =?UTF-8?q?=EB=A5=BC=20items+item=5Fdetails+prices=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EstimatePriceService 생성: items+item_details+prices JOIN 기반 단가 조회 - item_details.product_category/part_type/specification 컬럼 매핑 - items.attributes JSON으로 model_name/finishing_type 추가 차원 처리 - 세션 내 캐시로 중복 조회 방지 - MigrateBDModelsPrices 커맨드: 레거시 BDmodels + kd_price_tables → 85건 마이그레이션 - KyungdongFormulaHandler: KdPriceTable 의존 제거 → EstimatePriceService 사용 - FormulaEvaluatorService: W1 마진 140→160, 면적 공식 W1×(H1+550) 수정 - 가이드레일 H0+250, 케이스/L바/평철 W0+220 (레거시 일치) Co-Authored-By: Claude Opus 4.5 --- .../Commands/MigrateBDModelsPrices.php | 363 ++++++++++++++++++ app/Services/Quote/EstimatePriceService.php | 334 ++++++++++++++++ .../Quote/FormulaEvaluatorService.php | 14 +- .../Handlers/KyungdongFormulaHandler.php | 132 ++----- 4 files changed, 745 insertions(+), 98 deletions(-) create mode 100644 app/Console/Commands/MigrateBDModelsPrices.php create mode 100644 app/Services/Quote/EstimatePriceService.php diff --git a/app/Console/Commands/MigrateBDModelsPrices.php b/app/Console/Commands/MigrateBDModelsPrices.php new file mode 100644 index 0000000..b1a41fb --- /dev/null +++ b/app/Console/Commands/MigrateBDModelsPrices.php @@ -0,0 +1,363 @@ +option('dry-run'); + + $this->info('=== 경동 견적 단가 마이그레이션 ==='); + $this->info($dryRun ? '[DRY RUN] 실제 변경 없음' : '[LIVE] DB에 반영합니다'); + $this->newLine(); + + DB::beginTransaction(); + + try { + // 1. 레거시 BDmodels (chandj DB) + $this->migrateBDModels($dryRun); + + // 2. kd_price_tables (motor, shaft, pipe, angle, raw_material) + $this->migrateKdPriceTables($dryRun); + + if ($dryRun) { + DB::rollBack(); + $this->warn('[DRY RUN] 롤백 완료'); + } else { + DB::commit(); + $this->info('커밋 완료'); + } + + $this->newLine(); + $this->info("생성: {$this->created}건, 스킵: {$this->skipped}건"); + + return Command::SUCCESS; + } catch (\Exception $e) { + DB::rollBack(); + $this->error("오류: {$e->getMessage()}"); + + return Command::FAILURE; + } + } + + /** + * 레거시 chandj.BDmodels → items + item_details + prices + */ + private function migrateBDModels(bool $dryRun): void + { + $this->info('--- BDmodels (레거시) ---'); + + // chandj DB에서 BDmodels 조회 (chandj connection 사용) + $rows = DB::connection('chandj')->select(" + SELECT model_name, seconditem, finishing_type, spec, unitprice, description + FROM BDmodels + WHERE is_deleted = 0 + ORDER BY model_name, seconditem, finishing_type, spec + "); + + foreach ($rows as $row) { + $modelName = trim($row->model_name ?? ''); + $secondItem = trim($row->seconditem ?? ''); + $finishingType = trim($row->finishing_type ?? ''); + $spec = trim($row->spec ?? ''); + $unitPrice = (float) str_replace(',', '', $row->unitprice ?? '0'); + + // finishing_type 정규화: 'SUS마감' → 'SUS', 'EGI마감' → 'EGI' + $finishingType = str_replace('마감', '', $finishingType); + + if (empty($secondItem) || $unitPrice <= 0) { + $this->skipped++; + + continue; + } + + // 코드 생성 + $codeParts = ['BD', $secondItem]; + if ($modelName) { + $codeParts[] = $modelName; + } + if ($finishingType) { + $codeParts[] = $finishingType; + } + if ($spec) { + $codeParts[] = $spec; + } + $code = implode('-', $codeParts); + + // 이름 생성 + $nameParts = [$secondItem]; + if ($modelName) { + $nameParts[] = $modelName; + } + if ($finishingType) { + $nameParts[] = $finishingType; + } + if ($spec) { + $nameParts[] = $spec; + } + $name = implode(' ', $nameParts); + + $this->createEstimateItem( + code: $code, + name: $name, + productCategory: 'bdmodels', + partType: $secondItem, + specification: $spec ?: null, + attributes: array_filter([ + 'model_name' => $modelName ?: null, + 'finishing_type' => $finishingType ?: null, + 'bdmodel_source' => 'BDmodels', + 'description' => $row->description ?: null, + ]), + salesPrice: $unitPrice, + note: 'BDmodels 마이그레이션', + dryRun: $dryRun + ); + } + } + + /** + * kd_price_tables → items + item_details + prices + */ + private function migrateKdPriceTables(bool $dryRun): void + { + $this->info('--- kd_price_tables ---'); + + $rows = DB::table('kd_price_tables') + ->where('tenant_id', self::TENANT_ID) + ->where('is_active', true) + ->where('table_type', '!=', 'bdmodels') // BDmodels는 위에서 처리 + ->orderBy('table_type') + ->orderBy('item_code') + ->get(); + + foreach ($rows as $row) { + $tableType = $row->table_type; + $unitPrice = (float) $row->unit_price; + + if ($unitPrice <= 0) { + $this->skipped++; + + continue; + } + + switch ($tableType) { + case 'motor': + $this->migrateMotor($row, $dryRun); + break; + case 'shaft': + $this->migrateShaft($row, $dryRun); + break; + case 'pipe': + $this->migratePipe($row, $dryRun); + break; + case 'angle': + $this->migrateAngle($row, $dryRun); + break; + case 'raw_material': + $this->migrateRawMaterial($row, $dryRun); + break; + } + } + } + + private function migrateMotor(object $row, bool $dryRun): void + { + $category = $row->category; // 150K, 300K, 매립형, 노출형 등 + $code = "EST-MOTOR-{$category}"; + $name = "모터/제어기 {$category}"; + + $this->createEstimateItem( + code: $code, + name: $name, + productCategory: 'motor', + partType: $category, + specification: $row->spec2 ?? null, + attributes: ['price_unit' => $row->unit ?? 'EA'], + salesPrice: (float) $row->unit_price, + note: 'kd_price_tables motor 마이그레이션', + dryRun: $dryRun + ); + } + + private function migrateShaft(object $row, bool $dryRun): void + { + $size = $row->spec1; // 인치 + $length = $row->spec2; // 길이 + $code = "EST-SHAFT-{$size}-{$length}"; + $name = "감기샤프트 {$size}인치 {$length}m"; + + $this->createEstimateItem( + code: $code, + name: $name, + productCategory: 'shaft', + partType: $size, + specification: $length, + attributes: ['price_unit' => $row->unit ?? 'EA'], + salesPrice: (float) $row->unit_price, + note: 'kd_price_tables shaft 마이그레이션', + dryRun: $dryRun + ); + } + + private function migratePipe(object $row, bool $dryRun): void + { + $thickness = $row->spec1; + $length = $row->spec2; + $code = "EST-PIPE-{$thickness}-{$length}"; + $name = "각파이프 {$thickness}T {$length}mm"; + + $this->createEstimateItem( + code: $code, + name: $name, + productCategory: 'pipe', + partType: $thickness, + specification: $length, + attributes: ['price_unit' => $row->unit ?? 'EA'], + salesPrice: (float) $row->unit_price, + note: 'kd_price_tables pipe 마이그레이션', + dryRun: $dryRun + ); + } + + private function migrateAngle(object $row, bool $dryRun): void + { + $category = $row->category; // 스크린용, 철재용 + $bracketSize = $row->spec1; // 530*320, 600*350, 690*390 + $angleType = $row->spec2; // 앵글3T, 앵글4T + $code = "EST-ANGLE-{$category}-{$bracketSize}-{$angleType}"; + $name = "앵글 {$category} {$bracketSize} {$angleType}"; + + $this->createEstimateItem( + code: $code, + name: $name, + productCategory: 'angle', + partType: $category, + specification: $bracketSize, + attributes: [ + 'angle_type' => $angleType, + 'price_unit' => $row->unit ?? 'EA', + ], + salesPrice: (float) $row->unit_price, + note: 'kd_price_tables angle 마이그레이션', + dryRun: $dryRun + ); + } + + private function migrateRawMaterial(object $row, bool $dryRun): void + { + $name = $row->item_name; + $code = 'EST-RAW-'.preg_replace('/[^A-Za-z0-9가-힣]/', '', $name); + + $this->createEstimateItem( + code: $code, + name: $name, + productCategory: 'raw_material', + partType: $name, + specification: $row->spec1 ?? null, + attributes: ['price_unit' => $row->unit ?? 'EA'], + salesPrice: (float) $row->unit_price, + note: 'kd_price_tables raw_material 마이그레이션', + dryRun: $dryRun + ); + } + + /** + * 견적 품목 생성 (items + item_details + prices) + */ + private function createEstimateItem( + string $code, + string $name, + string $productCategory, + string $partType, + ?string $specification, + array $attributes, + float $salesPrice, + string $note, + bool $dryRun + ): void { + // 중복 체크 (code 기준) + $existing = DB::table('items') + ->where('tenant_id', self::TENANT_ID) + ->where('code', $code) + ->whereNull('deleted_at') + ->first(); + + if ($existing) { + $this->line(" [스킵] {$code} - 이미 존재"); + $this->skipped++; + + return; + } + + $this->line(" [생성] {$code} ({$name}) = {$salesPrice}"); + + if ($dryRun) { + $this->created++; + + return; + } + + $now = now(); + + // 1. items + $itemId = DB::table('items')->insertGetId([ + 'tenant_id' => self::TENANT_ID, + 'item_type' => 'PT', + 'code' => $code, + 'name' => $name, + 'unit' => 'EA', + 'attributes' => json_encode($attributes, JSON_UNESCAPED_UNICODE), + 'is_active' => true, + 'created_at' => $now, + 'updated_at' => $now, + ]); + + // 2. item_details + DB::table('item_details')->insert([ + 'item_id' => $itemId, + 'product_category' => $productCategory, + 'part_type' => $partType, + 'specification' => $specification, + 'item_name' => $name, + 'is_purchasable' => true, + 'created_at' => $now, + 'updated_at' => $now, + ]); + + // 3. prices + DB::table('prices')->insert([ + 'tenant_id' => self::TENANT_ID, + 'item_type_code' => 'PT', + 'item_id' => $itemId, + 'sales_price' => $salesPrice, + 'effective_from' => $now->toDateString(), + 'status' => 'active', + 'note' => $note, + 'created_at' => $now, + 'updated_at' => $now, + ]); + + $this->created++; + } +} \ No newline at end of file diff --git a/app/Services/Quote/EstimatePriceService.php b/app/Services/Quote/EstimatePriceService.php new file mode 100644 index 0000000..f66f336 --- /dev/null +++ b/app/Services/Quote/EstimatePriceService.php @@ -0,0 +1,334 @@ +tenantId = $tenantId; + } + + // ========================================================================= + // BDmodels 단가 조회 (절곡품) + // ========================================================================= + + /** + * BDmodels 단가 조회 + * + * item_details 컬럼 매핑: + * product_category = 'bdmodels' + * part_type = seconditem (가이드레일, 케이스, 마구리, L-BAR, 하단마감재, 보강평철, 연기차단재) + * specification = spec (120*70, 500*380 등) + * items.attributes: + * model_name = KSS01, KSS02 등 + * finishing_type = SUS, EGI + */ + public function getBDModelPrice( + string $secondItem, + ?string $modelName = null, + ?string $finishingType = null, + ?string $spec = null + ): float { + $cacheKey = "bdmodel:{$secondItem}:{$modelName}:{$finishingType}:{$spec}"; + + if (isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $query = DB::table('items') + ->join('item_details', 'item_details.item_id', '=', 'items.id') + ->join('prices', 'prices.item_id', '=', 'items.id') + ->where('items.tenant_id', $this->tenantId) + ->where('items.is_active', true) + ->whereNull('items.deleted_at') + ->where('item_details.product_category', 'bdmodels') + ->where('item_details.part_type', $secondItem); + + if ($modelName) { + $query->where('items.attributes->model_name', $modelName); + } else { + $query->where(function ($q) { + $q->whereNull('items.attributes->model_name') + ->orWhere('items.attributes->model_name', ''); + }); + } + + if ($finishingType) { + $query->where('items.attributes->finishing_type', $finishingType); + } + + if ($spec) { + $query->where('item_details.specification', $spec); + } + + // 현재 유효한 단가 + $today = now()->toDateString(); + $query->where('prices.effective_from', '<=', $today) + ->where(function ($q) use ($today) { + $q->whereNull('prices.effective_to') + ->orWhere('prices.effective_to', '>=', $today); + }) + ->whereNull('prices.deleted_at'); + + $price = (float) ($query->value('prices.sales_price') ?? 0); + + $this->cache[$cacheKey] = $price; + + return $price; + } + + /** + * 케이스 단가 + */ + public function getCasePrice(string $spec): float + { + return $this->getBDModelPrice('케이스', null, null, $spec); + } + + /** + * 가이드레일 단가 + */ + public function getGuideRailPrice(string $modelName, string $finishingType, string $spec): float + { + return $this->getBDModelPrice('가이드레일', $modelName, $finishingType, $spec); + } + + /** + * 하단마감재(하장바) 단가 + */ + public function getBottomBarPrice(string $modelName, string $finishingType): float + { + return $this->getBDModelPrice('하단마감재', $modelName, $finishingType); + } + + /** + * L-BAR 단가 + */ + public function getLBarPrice(string $modelName): float + { + return $this->getBDModelPrice('L-BAR', $modelName); + } + + /** + * 보강평철 단가 + */ + public function getFlatBarPrice(): float + { + return $this->getBDModelPrice('보강평철'); + } + + /** + * 케이스 마구리 단가 + */ + public function getCaseCapPrice(string $spec): float + { + return $this->getBDModelPrice('마구리', null, null, $spec); + } + + /** + * 케이스용 연기차단재 단가 + */ + public function getCaseSmokeBlockPrice(): float + { + return $this->getBDModelPrice('케이스용 연기차단재'); + } + + /** + * 가이드레일용 연기차단재 단가 + */ + public function getRailSmokeBlockPrice(): float + { + return $this->getBDModelPrice('가이드레일용 연기차단재'); + } + + // ========================================================================= + // 모터/제어기 단가 + // ========================================================================= + + /** + * 모터 단가 + */ + public function getMotorPrice(string $motorCapacity): float + { + return $this->getEstimatePartPrice('motor', $motorCapacity); + } + + /** + * 제어기 단가 + */ + public function getControllerPrice(string $controllerType): float + { + return $this->getEstimatePartPrice('motor', $controllerType); + } + + // ========================================================================= + // 부자재 단가 + // ========================================================================= + + /** + * 샤프트 단가 + */ + public function getShaftPrice(string $size, float $length): float + { + $lengthStr = number_format($length, 1, '.', ''); + $cacheKey = "shaft:{$size}:{$lengthStr}"; + + if (isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $price = $this->getEstimatePartPriceBySpec('shaft', $size, $lengthStr); + $this->cache[$cacheKey] = $price; + + return $price; + } + + /** + * 파이프 단가 + */ + public function getPipePrice(string $thickness, int $length): float + { + return $this->getEstimatePartPriceBySpec('pipe', $thickness, (string) $length); + } + + /** + * 앵글 단가 + */ + public function getAnglePrice(string $type, string $bracketSize, string $angleType): float + { + $cacheKey = "angle:{$type}:{$bracketSize}:{$angleType}"; + + if (isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $today = now()->toDateString(); + + $price = (float) (DB::table('items') + ->join('item_details', 'item_details.item_id', '=', 'items.id') + ->join('prices', 'prices.item_id', '=', 'items.id') + ->where('items.tenant_id', $this->tenantId) + ->where('items.is_active', true) + ->whereNull('items.deleted_at') + ->where('item_details.product_category', 'angle') + ->where('item_details.part_type', $type) + ->where('item_details.specification', $bracketSize) + ->where('items.attributes->angle_type', $angleType) + ->where('prices.effective_from', '<=', $today) + ->where(function ($q) use ($today) { + $q->whereNull('prices.effective_to') + ->orWhere('prices.effective_to', '>=', $today); + }) + ->whereNull('prices.deleted_at') + ->value('prices.sales_price') ?? 0); + + $this->cache[$cacheKey] = $price; + + return $price; + } + + /** + * 원자재 단가 + */ + public function getRawMaterialPrice(string $materialName): float + { + return $this->getEstimatePartPrice('raw_material', $materialName); + } + + // ========================================================================= + // 내부 헬퍼 + // ========================================================================= + + /** + * product_category + part_type 기반 단가 조회 + */ + private function getEstimatePartPrice(string $productCategory, string $partType): float + { + $cacheKey = "{$productCategory}:{$partType}"; + + if (isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $today = now()->toDateString(); + + $price = (float) (DB::table('items') + ->join('item_details', 'item_details.item_id', '=', 'items.id') + ->join('prices', 'prices.item_id', '=', 'items.id') + ->where('items.tenant_id', $this->tenantId) + ->where('items.is_active', true) + ->whereNull('items.deleted_at') + ->where('item_details.product_category', $productCategory) + ->where('item_details.part_type', $partType) + ->where('prices.effective_from', '<=', $today) + ->where(function ($q) use ($today) { + $q->whereNull('prices.effective_to') + ->orWhere('prices.effective_to', '>=', $today); + }) + ->whereNull('prices.deleted_at') + ->value('prices.sales_price') ?? 0); + + $this->cache[$cacheKey] = $price; + + return $price; + } + + /** + * product_category + spec1 + spec2 기반 단가 조회 + */ + private function getEstimatePartPriceBySpec(string $productCategory, string $spec1, string $spec2): float + { + $cacheKey = "{$productCategory}:{$spec1}:{$spec2}"; + + if (isset($this->cache[$cacheKey])) { + return $this->cache[$cacheKey]; + } + + $today = now()->toDateString(); + + $price = (float) (DB::table('items') + ->join('item_details', 'item_details.item_id', '=', 'items.id') + ->join('prices', 'prices.item_id', '=', 'items.id') + ->where('items.tenant_id', $this->tenantId) + ->where('items.is_active', true) + ->whereNull('items.deleted_at') + ->where('item_details.product_category', $productCategory) + ->where('item_details.part_type', $spec1) + ->where('item_details.specification', $spec2) + ->where('prices.effective_from', '<=', $today) + ->where(function ($q) use ($today) { + $q->whereNull('prices.effective_to') + ->orWhere('prices.effective_to', '>=', $today); + }) + ->whereNull('prices.deleted_at') + ->value('prices.sales_price') ?? 0); + + $this->cache[$cacheKey] = $price; + + return $price; + } + + /** + * 캐시 초기화 + */ + public function clearCache(): void + { + $this->cache = []; + } +} \ No newline at end of file diff --git a/app/Services/Quote/FormulaEvaluatorService.php b/app/Services/Quote/FormulaEvaluatorService.php index 7217ba0..d3942d8 100644 --- a/app/Services/Quote/FormulaEvaluatorService.php +++ b/app/Services/Quote/FormulaEvaluatorService.php @@ -658,7 +658,7 @@ public function calculateBomWithDebug( $marginW = 110; // 철재 마진 $marginH = 350; } else { - $marginW = 140; // 스크린 기본 마진 + $marginW = 160; // 스크린 기본 마진 $marginH = 350; } @@ -1625,9 +1625,9 @@ private function calculateKyungdongBom( $handler = new KyungdongFormulaHandler; // Step 3: 경동 전용 변수 계산 - $W1 = $W0 + 140; + $W1 = $W0 + 160; $H1 = $H0 + 350; - $area = ($W0 * ($H0 + 550)) / 1000000; + $area = ($W1 * ($H1 + 550)) / 1000000; // 중량 계산 (제품타입별) if ($productType === 'steel') { @@ -1665,8 +1665,8 @@ private function calculateKyungdongBom( [ 'var' => 'W1', 'desc' => '제작 폭', - 'formula' => 'W0 + 140', - 'calculation' => "{$W0} + 140", + 'formula' => 'W0 + 160', + 'calculation' => "{$W0} + 160", 'result' => $W1, 'unit' => 'mm', ], @@ -1681,8 +1681,8 @@ private function calculateKyungdongBom( [ 'var' => 'AREA', 'desc' => '면적', - 'formula' => '(W0 × (H0 + 550)) / 1,000,000', - 'calculation' => "({$W0} × ({$H0} + 550)) / 1,000,000", + 'formula' => '(W1 × (H1 + 550)) / 1,000,000', + 'calculation' => "({$W1} × ({$H1} + 550)) / 1,000,000", 'result' => round($area, 4), 'unit' => '㎡', ], diff --git a/app/Services/Quote/Handlers/KyungdongFormulaHandler.php b/app/Services/Quote/Handlers/KyungdongFormulaHandler.php index 6e27858..e00f4e5 100644 --- a/app/Services/Quote/Handlers/KyungdongFormulaHandler.php +++ b/app/Services/Quote/Handlers/KyungdongFormulaHandler.php @@ -2,7 +2,7 @@ namespace App\Services\Quote\Handlers; -use App\Models\Kyungdong\KdPriceTable; +use App\Services\Quote\EstimatePriceService; /** * 경동기업 전용 견적 계산 핸들러 @@ -14,6 +14,13 @@ class KyungdongFormulaHandler { private const TENANT_ID = 287; + private EstimatePriceService $priceService; + + public function __construct(?EstimatePriceService $priceService = null) + { + $this->priceService = $priceService ?? new EstimatePriceService(self::TENANT_ID); + } + // ========================================================================= // 모터 용량 계산 // ========================================================================= @@ -222,9 +229,12 @@ private function getMotorCapacityByWeight(float $weight, ?string $bracketInch = */ public function calculateScreenPrice(float $width, float $height): array { - // 면적 계산: W × (H + 550) / 1,000,000 - $calculateHeight = $height + 550; - $area = ($width * $calculateHeight) / 1000000; + // 면적 계산: W1 × (H1 + 550) / 1,000,000 + // W1 = W0 + 160, H1 = H0 + 350 (레거시 5130 공식) + $W1 = $width + 160; + $H1 = $height + 350; + $calculateHeight = $H1 + 550; + $area = ($W1 * $calculateHeight) / 1000000; // 원자재 단가 조회 (실리카/스크린) $unitPrice = $this->getRawMaterialPrice('실리카'); @@ -237,117 +247,55 @@ public function calculateScreenPrice(float $width, float $height): array } // ========================================================================= - // 단가 조회 메서드 (KdPriceTable 사용) + // 단가 조회 메서드 (EstimatePriceService 사용) // ========================================================================= - /** - * BDmodels 테이블에서 단가 조회 - * - * @param string $modelName 모델코드 (KSS01, KWS01 등) - * @param string $secondItem 부품분류 (케이스, 가이드레일, 하단마감재, L-BAR 등) - * @param string|null $finishingType 마감재질 (SUS, EGI) - * @param string|null $spec 규격 (120*70, 650*550 등) - * @return float 단가 - */ - public function getBDModelPrice( - string $modelName, - string $secondItem, - ?string $finishingType = null, - ?string $spec = null - ): float { - // BDmodels는 복잡한 구조이므로 items 테이블의 기존 데이터 활용 - // TODO: 필요시 kd_price_tables TYPE_BDMODELS 추가 - return 0.0; - } - - /** - * price_* 테이블에서 단가 조회 (모터, 샤프트, 파이프, 앵글) - * - * @param string $tableName 테이블명 (motor, shaft, pipe, angle) - * @param array $conditions 조회 조건 - * @return float 단가 - */ - public function getPriceFromTable(string $tableName, array $conditions): float - { - $query = KdPriceTable::where('table_type', $tableName)->active(); - - foreach ($conditions as $field => $value) { - $query->where($field, $value); - } - - $record = $query->first(); - - return (float) ($record?->unit_price ?? 0); - } - /** * 원자재 단가 조회 - * - * @param string $materialName 원자재명 (실리카, 스크린 등) - * @return float 단가 */ public function getRawMaterialPrice(string $materialName): float { - return KdPriceTable::getRawMaterialPrice($materialName); + return $this->priceService->getRawMaterialPrice($materialName); } /** * 모터 단가 조회 - * - * @param string $motorCapacity 모터 용량 (150K, 300K 등) - * @return float 단가 */ public function getMotorPrice(string $motorCapacity): float { - return KdPriceTable::getMotorPrice($motorCapacity); + return $this->priceService->getMotorPrice($motorCapacity); } /** * 제어기 단가 조회 - * - * @param string $controllerType 제어기 타입 (매립형, 노출형, 뒷박스) - * @return float 단가 */ public function getControllerPrice(string $controllerType): float { - return KdPriceTable::getControllerPrice($controllerType); + return $this->priceService->getControllerPrice($controllerType); } /** * 샤프트 단가 조회 - * - * @param string $size 사이즈 (3, 4, 5인치) - * @param float $length 길이 (m 단위) - * @return float 단가 */ public function getShaftPrice(string $size, float $length): float { - return KdPriceTable::getShaftPrice($size, $length); + return $this->priceService->getShaftPrice($size, $length); } /** * 파이프 단가 조회 - * - * @param string $thickness 두께 (1.4 등) - * @param int $length 길이 (3000, 6000) - * @return float 단가 */ public function getPipePrice(string $thickness, int $length): float { - return KdPriceTable::getPipePrice($thickness, $length); + return $this->priceService->getPipePrice($thickness, $length); } /** * 앵글 단가 조회 - * - * @param string $type 타입 (스크린용, 철재용) - * @param string $bracketSize 브라켓크기 (530*320, 600*350, 690*390) - * @param string $angleType 앵글타입 (앵글3T, 앵글4T) - * @return float 단가 */ public function getAnglePrice(string $type, string $bracketSize, string $angleType): float { - return KdPriceTable::getAnglePrice($type, $bracketSize, $angleType); + return $this->priceService->getAnglePrice($type, $bracketSize, $angleType); } // ========================================================================= @@ -376,18 +324,18 @@ public function calculateSteelItems(array $params): array // 절곡품 관련 파라미터 $caseSpec = $params['case_spec'] ?? '500*380'; - $caseLength = (float) ($params['case_length'] ?? $width); // mm 단위 + $caseLength = (float) ($params['case_length'] ?? ($width + 220)); // mm 단위 (레거시: W0+220) $guideType = $params['guide_type'] ?? '벽면형'; // 벽면형, 측면형, 혼합형 $guideSpec = $params['guide_spec'] ?? '120*70'; // 120*70, 120*100 - $guideLength = (float) ($params['guide_length'] ?? ($height + 550)) / 1000; // m 단위 - $bottomBarLength = (float) ($params['bottombar_length'] ?? $width) / 1000; // m 단위 - $lbarLength = (float) ($params['lbar_length'] ?? $width) / 1000; // m 단위 - $flatBarLength = (float) ($params['flatbar_length'] ?? $width) / 1000; // m 단위 + $guideLength = (float) ($params['guide_length'] ?? ($height + 250)) / 1000; // m 단위 (레거시: H0+250) + $bottomBarLength = (float) ($params['bottombar_length'] ?? $width) / 1000; // m 단위 (레거시: W0) + $lbarLength = (float) ($params['lbar_length'] ?? ($width + 220)) / 1000; // m 단위 (레거시: W0+220) + $flatBarLength = (float) ($params['flatbar_length'] ?? ($width + 220)) / 1000; // m 단위 (레거시: W0+220) $weightPlateQty = (int) ($params['weight_plate_qty'] ?? 0); // 무게평철 수량 $roundBarQty = (int) ($params['round_bar_qty'] ?? 0); // 환봉 수량 // 1. 케이스 (단가/1000 × 길이mm × 수량) - $casePrice = KdPriceTable::getCasePrice($caseSpec); + $casePrice = $this->priceService->getCasePrice($caseSpec); if ($casePrice > 0 && $caseLength > 0) { $totalPrice = ($casePrice / 1000) * $caseLength * $quantity; $items[] = [ @@ -402,7 +350,7 @@ public function calculateSteelItems(array $params): array } // 2. 케이스용 연기차단재 (단가 × 길이m × 수량) - $caseSmokePrice = KdPriceTable::getCaseSmokeBlockPrice(); + $caseSmokePrice = $this->priceService->getCaseSmokeBlockPrice(); if ($caseSmokePrice > 0 && $caseLength > 0) { $lengthM = $caseLength / 1000; $items[] = [ @@ -417,7 +365,7 @@ public function calculateSteelItems(array $params): array } // 3. 케이스 마구리 (단가 × 수량) - $caseCapPrice = KdPriceTable::getCaseCapPrice($caseSpec); + $caseCapPrice = $this->priceService->getCaseCapPrice($caseSpec); if ($caseCapPrice > 0) { $capQty = 2 * $quantity; // 좌우 2개 $items[] = [ @@ -436,7 +384,7 @@ public function calculateSteelItems(array $params): array $items = array_merge($items, $guideItems); // 5. 레일용 연기차단재 (단가 × 길이m × 2 × 수량) - $railSmokePrice = KdPriceTable::getRailSmokeBlockPrice(); + $railSmokePrice = $this->priceService->getRailSmokeBlockPrice(); if ($railSmokePrice > 0 && $guideLength > 0) { $railSmokeQty = 2 * $quantity; // 좌우 2개 $items[] = [ @@ -451,7 +399,7 @@ public function calculateSteelItems(array $params): array } // 6. 하장바 (단가 × 길이m × 수량) - $bottomBarPrice = KdPriceTable::getBottomBarPrice($modelName, $finishingType); + $bottomBarPrice = $this->priceService->getBottomBarPrice($modelName, $finishingType); if ($bottomBarPrice > 0 && $bottomBarLength > 0) { $items[] = [ 'category' => 'steel', @@ -465,7 +413,7 @@ public function calculateSteelItems(array $params): array } // 7. L바 (단가 × 길이m × 수량) - $lbarPrice = KdPriceTable::getLBarPrice($modelName); + $lbarPrice = $this->priceService->getLBarPrice($modelName); if ($lbarPrice > 0 && $lbarLength > 0) { $items[] = [ 'category' => 'steel', @@ -479,7 +427,7 @@ public function calculateSteelItems(array $params): array } // 8. 보강평철 (단가 × 길이m × 수량) - $flatBarPrice = KdPriceTable::getFlatBarPrice(); + $flatBarPrice = $this->priceService->getFlatBarPrice(); if ($flatBarPrice > 0 && $flatBarLength > 0) { $items[] = [ 'category' => 'steel', @@ -551,7 +499,7 @@ private function calculateGuideRails( switch ($guideType) { case '벽면형': // 120*70 × 2개 - $price = KdPriceTable::getGuideRailPrice($modelName, $finishingType, '120*70'); + $price = $this->priceService->getGuideRailPrice($modelName, $finishingType, '120*70'); if ($price > 0) { $guideQty = 2 * $quantity; $items[] = [ @@ -568,7 +516,7 @@ private function calculateGuideRails( case '측면형': // 120*100 × 2개 - $price = KdPriceTable::getGuideRailPrice($modelName, $finishingType, '120*100'); + $price = $this->priceService->getGuideRailPrice($modelName, $finishingType, '120*100'); if ($price > 0) { $guideQty = 2 * $quantity; $items[] = [ @@ -585,8 +533,8 @@ private function calculateGuideRails( case '혼합형': // 120*70 × 1개 + 120*100 × 1개 - $price70 = KdPriceTable::getGuideRailPrice($modelName, $finishingType, '120*70'); - $price100 = KdPriceTable::getGuideRailPrice($modelName, $finishingType, '120*100'); + $price70 = $this->priceService->getGuideRailPrice($modelName, $finishingType, '120*70'); + $price100 = $this->priceService->getGuideRailPrice($modelName, $finishingType, '120*100'); if ($price70 > 0) { $items[] = [ @@ -707,8 +655,10 @@ public function calculateDynamicItems(array $inputs): array $bracketInch = $inputs['bracket_inch'] ?? '5'; $productType = $inputs['product_type'] ?? 'screen'; - // 중량 계산 (5130 로직) - $area = ($width * ($height + 550)) / 1000000; + // 중량 계산 (5130 로직) - W1, H1 기반 + $W1 = $width + 160; + $H1 = $height + 350; + $area = ($W1 * ($H1 + 550)) / 1000000; $weight = $area * ($productType === 'steel' ? 25 : 2) + ($width / 1000) * 14.17; // 모터 용량/브라켓 크기 계산