Files
sam-api/app/Services/Quote/EstimatePriceService.php

389 lines
12 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Services\Quote;
use App\Models\Items\Item;
use App\Models\Products\Price;
use Illuminate\Support\Facades\DB;
/**
* 견적 단가 조회 서비스
*
* items + item_details + prices 테이블 기반 단가 조회
* kd_price_tables 대체
*/
class EstimatePriceService
{
private int $tenantId;
/** @var array 단가 캐시 (세션 내 중복 조회 방지) */
private array $cache = [];
public function __construct(int $tenantId)
{
$this->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('가이드레일용 연기차단재');
}
// =========================================================================
// 모터/제어기 단가
// =========================================================================
/**
* 모터 단가
*
* chandj col2는 '150K(S)', '300K(S)', '300K' 다양한 형식
* handler는 '150K', '300K' 단순 용량으로 호출
* LIKE 매칭 + 380V 기본 전압 필터 적용
*/
public function getMotorPrice(string $motorCapacity, string $voltage = '380'): float
{
$cacheKey = "motor:{$motorCapacity}:{$voltage}";
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', 'motor')
->where('item_details.part_type', 'LIKE', "{$motorCapacity}%")
->where('items.attributes->voltage', $voltage)
->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 getControllerPrice(string $controllerType): float
{
return $this->getEstimatePartPrice('controller', $controllerType);
}
// =========================================================================
// 부자재 단가
// =========================================================================
/**
* 샤프트 단가
*
* chandj col10은 '0.3', '3', '6' 혼재 포맷
* 정수면 '6', 소수면 '0.3' 그대로 저장됨
*/
public function getShaftPrice(string $size, float $length): float
{
// chandj 원본 포맷에 맞게 변환: 정수면 정수형, 소수면 소수형
$lengthStr = ($length == (int) $length) ? (string) (int) $length : (string) $length;
$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);
}
/**
* 모터 받침용 앵글 단가 (bracket angle)
*
* 5130: calculateAngle(qty, itemList, '스크린용') col2 검색
* chandj col2 : '스크린용', '철제300K', '철제400K', '철제800K'
*/
public function getAnglePrice(string $searchOption): float
{
$cacheKey = "angle_bracket:{$searchOption}";
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_bracket')
->where('item_details.part_type', $searchOption)
->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;
}
/**
* 부자재용 앵글 단가 (main angle)
*
* 5130: calculateMainAngle(1, itemList, '앵글3T', '2.5') col4+col10 검색
*/
public function getMainAnglePrice(string $angleType, string $size): float
{
$cacheKey = "angle_main:{$angleType}:{$size}";
if (isset($this->cache[$cacheKey])) {
return $this->cache[$cacheKey];
}
$price = $this->getEstimatePartPriceBySpec('angle_main', $angleType, $size);
$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 = [];
}
}