feat: 견적 산출 서비스 prices 테이블 연동

- Price 모델에 getCurrentPrice(), getSalesPriceByItemCode() 메서드 추가
- Price 모델에 STATUS_*, ITEM_TYPE_* 상수 추가
- QuoteCalculationService에 setTenantId(), getUnitPrice() 메서드 추가
- 스크린 품목 단가: 원단, 케이스, 브라켓, 인건비 prices 조회로 변경
- 철재 품목 단가: 철판, 용접, 표면처리, 가공비 prices 조회로 변경
- 모터 용량별 단가: 50W~300W prices 조회로 변경
- 모든 단가는 prices 조회 실패 시 기존 하드코딩 값을 fallback으로 사용
This commit is contained in:
2025-12-19 16:20:38 +09:00
parent 8f1292f7c4
commit 4d3085e705
2 changed files with 171 additions and 20 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Services\Quote;
use App\Models\Products\Price;
use App\Models\Quote\Quote;
use App\Services\Service;
@@ -13,10 +14,39 @@
*/
class QuoteCalculationService extends Service
{
private ?int $tenantId = null;
public function __construct(
private FormulaEvaluatorService $formulaEvaluator
) {}
/**
* 테넌트 ID 설정
*/
public function setTenantId(int $tenantId): self
{
$this->tenantId = $tenantId;
return $this;
}
/**
* 품목 코드로 단가 조회 (prices 테이블 연동)
*
* @param string $itemCode 품목 코드
* @param float $fallback 조회 실패 시 기본값
*/
private function getUnitPrice(string $itemCode, float $fallback = 0): float
{
if (! $this->tenantId) {
return $fallback;
}
$price = Price::getSalesPriceByItemCode($this->tenantId, $itemCode);
return $price > 0 ? $price : $fallback;
}
/**
* 견적 자동산출 실행
*
@@ -209,6 +239,7 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
$items = [];
// 1. 스크린 원단
$fabricPrice = $this->getUnitPrice('SCR-FABRIC-001', 25000);
$items[] = [
'item_code' => 'SCR-FABRIC-001',
'item_name' => '스크린 원단',
@@ -216,13 +247,14 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
'unit' => 'm²',
'base_quantity' => 1,
'calculated_quantity' => $outputs['AREA'] * $qty,
'unit_price' => 25000,
'total_price' => $outputs['AREA'] * $qty * 25000,
'unit_price' => $fabricPrice,
'total_price' => $outputs['AREA'] * $qty * $fabricPrice,
'formula' => 'AREA * QTY',
'formula_category' => 'material',
];
// 2. 케이스
$casePrice = $this->getUnitPrice('SCR-CASE-001', 85000);
$items[] = [
'item_code' => 'SCR-CASE-001',
'item_name' => '알루미늄 케이스',
@@ -230,8 +262,8 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
'unit' => 'EA',
'base_quantity' => 1,
'calculated_quantity' => $qty,
'unit_price' => 85000,
'total_price' => $qty * 85000,
'unit_price' => $casePrice,
'total_price' => $qty * $casePrice,
'formula' => 'QTY',
'formula_category' => 'material',
];
@@ -252,6 +284,7 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
];
// 4. 브라켓
$bracketPrice = $this->getUnitPrice('SCR-BRACKET-001', 15000);
$items[] = [
'item_code' => 'SCR-BRACKET-001',
'item_name' => '설치 브라켓',
@@ -259,8 +292,8 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
'unit' => 'SET',
'base_quantity' => 2,
'calculated_quantity' => 2 * $qty,
'unit_price' => 15000,
'total_price' => 2 * $qty * 15000,
'unit_price' => $bracketPrice,
'total_price' => 2 * $qty * $bracketPrice,
'formula' => '2 * QTY',
'formula_category' => 'material',
];
@@ -272,6 +305,7 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
['min' => 10, 'max' => null, 'result' => 4],
], 2);
$laborPrice = $this->getUnitPrice('LAB-INSTALL-001', 50000);
$items[] = [
'item_code' => 'LAB-INSTALL-001',
'item_name' => '설치 인건비',
@@ -279,8 +313,8 @@ private function generateScreenItems(array $inputs, array $outputs, int $qty): a
'unit' => 'HR',
'base_quantity' => $laborHours,
'calculated_quantity' => $laborHours * $qty,
'unit_price' => 50000,
'total_price' => $laborHours * $qty * 50000,
'unit_price' => $laborPrice,
'total_price' => $laborHours * $qty * $laborPrice,
'formula' => 'LABOR_HOURS * QTY',
'formula_category' => 'labor',
];
@@ -295,16 +329,18 @@ private function generateSteelItems(array $inputs, array $outputs, int $qty): ar
{
$items = [];
// 재질별 단가
$materialPrice = $this->formulaEvaluator->evaluateMapping($inputs['MATERIAL'], [
// 재질별 품목코드 및 단가 조회
$materialCode = 'STL-PLATE-'.strtoupper($inputs['MATERIAL']);
$fallbackMaterialPrice = $this->formulaEvaluator->evaluateMapping($inputs['MATERIAL'], [
['source' => 'ss304', 'result' => 4500],
['source' => 'ss316', 'result' => 6500],
['source' => 'galvanized', 'result' => 3000],
], 4500);
$materialPrice = $this->getUnitPrice($materialCode, $fallbackMaterialPrice);
// 1. 철판
$items[] = [
'item_code' => 'STL-PLATE-001',
'item_code' => $materialCode,
'item_name' => '철판 ('.$inputs['MATERIAL'].')',
'specification' => sprintf('%.0f x %.0f x %.1f mm', $outputs['W1'], $outputs['H1'], $inputs['THICKNESS']),
'unit' => 'kg',
@@ -318,6 +354,7 @@ private function generateSteelItems(array $inputs, array $outputs, int $qty): ar
// 2. 용접
$weldLength = ($outputs['W1'] + $outputs['H1']) * 2 / 1000; // m
$weldPrice = $this->getUnitPrice('STL-WELD-001', 15000);
$items[] = [
'item_code' => 'STL-WELD-001',
'item_name' => '용접 ('.$inputs['WELDING'].')',
@@ -325,21 +362,23 @@ private function generateSteelItems(array $inputs, array $outputs, int $qty): ar
'unit' => 'm',
'base_quantity' => $weldLength,
'calculated_quantity' => $weldLength * $qty,
'unit_price' => 15000,
'total_price' => $weldLength * $qty * 15000,
'unit_price' => $weldPrice,
'total_price' => $weldLength * $qty * $weldPrice,
'formula' => 'WELD_LENGTH * QTY',
'formula_category' => 'labor',
];
// 3. 표면처리
$finishPrice = $this->formulaEvaluator->evaluateMapping($inputs['FINISH'], [
$finishCode = 'STL-FINISH-'.strtoupper($inputs['FINISH']);
$fallbackFinishPrice = $this->formulaEvaluator->evaluateMapping($inputs['FINISH'], [
['source' => 'hairline', 'result' => 8000],
['source' => 'mirror', 'result' => 15000],
['source' => 'matte', 'result' => 5000],
], 8000);
$finishPrice = $this->getUnitPrice($finishCode, $fallbackFinishPrice);
$items[] = [
'item_code' => 'STL-FINISH-001',
'item_code' => $finishCode,
'item_name' => '표면처리 ('.$inputs['FINISH'].')',
'specification' => sprintf('%.2f m²', $outputs['AREA'] * $qty),
'unit' => 'm²',
@@ -352,6 +391,7 @@ private function generateSteelItems(array $inputs, array $outputs, int $qty): ar
];
// 4. 가공비
$processPrice = $this->getUnitPrice('STL-PROCESS-001', 50000);
$items[] = [
'item_code' => 'STL-PROCESS-001',
'item_name' => '가공비',
@@ -359,8 +399,8 @@ private function generateSteelItems(array $inputs, array $outputs, int $qty): ar
'unit' => 'EA',
'base_quantity' => 1,
'calculated_quantity' => $qty,
'unit_price' => 50000,
'total_price' => $qty * 50000,
'unit_price' => $processPrice,
'total_price' => $qty * $processPrice,
'formula' => 'QTY',
'formula_category' => 'labor',
];
@@ -400,17 +440,21 @@ private function calculateCosts(array $items): array
}
/**
* 모터 단가 조회
* 모터 단가 조회 (prices 테이블 연동)
*/
private function getMotorPrice(string $capacity): int
private function getMotorPrice(string $capacity): float
{
return match ($capacity) {
// 용량별 품목코드 및 기본 단가
$motorCode = 'SCR-MOTOR-'.$capacity;
$fallbackPrice = match ($capacity) {
'50W' => 120000,
'100W' => 150000,
'200W' => 200000,
'300W' => 280000,
default => 150000,
};
return $this->getUnitPrice($motorCode, $fallbackPrice);
}
/**