feat: 견적 BOM 자재 조회 기능 개선
- FormulaEvaluatorService: 품목마스터에서 규격/단위 조회 (getItemSpecAndUnit) - QuoteService: 저장된 calculation_inputs로 BOM 자재 계산 (calculateBomMaterials) - 견적 조회 시 bom_materials 데이터 자동 포함 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
namespace App\Services\Quote;
|
||||
|
||||
use App\Models\CategoryGroup;
|
||||
use App\Models\Items\Item;
|
||||
use App\Models\Products\Price;
|
||||
use App\Models\Quote\QuoteFormula;
|
||||
use App\Services\Service;
|
||||
@@ -479,11 +480,16 @@ public function executeAll(Collection $formulasByCategory, array $inputVariables
|
||||
? $this->evaluate($item->unit_price_formula)
|
||||
: $this->getItemPrice($item->item_code);
|
||||
|
||||
// 품목마스터에서 규격/단위 조회 (우선순위: 품목마스터 > 수식품목)
|
||||
$itemMasterData = $this->getItemSpecAndUnit($item->item_code);
|
||||
$specification = $itemMasterData['specification'] ?? $item->specification;
|
||||
$unit = $itemMasterData['unit'] ?? $item->unit ?? 'EA';
|
||||
|
||||
$items[] = [
|
||||
'item_code' => $item->item_code,
|
||||
'item_name' => $item->item_name,
|
||||
'specification' => $item->specification,
|
||||
'unit' => $item->unit,
|
||||
'specification' => $specification,
|
||||
'unit' => $unit,
|
||||
'quantity' => $quantity,
|
||||
'unit_price' => $unitPrice,
|
||||
'total_price' => $quantity * $unitPrice,
|
||||
@@ -729,6 +735,8 @@ public function calculateBomWithDebug(
|
||||
'item_code' => $bomItem['item_code'],
|
||||
'item_name' => $bomItem['item_name'],
|
||||
'item_category' => $itemCategory,
|
||||
'specification' => $bomItem['specification'] ?? null,
|
||||
'unit' => $bomItem['unit'] ?? 'EA',
|
||||
'quantity' => $displayQuantity,
|
||||
'quantity_formula' => $quantityFormula,
|
||||
'base_price' => $basePrice,
|
||||
@@ -1071,6 +1079,41 @@ private function getItemCategory(string $itemCode, int $tenantId): string
|
||||
return $category ?? '기타';
|
||||
}
|
||||
|
||||
/**
|
||||
* 품목마스터에서 규격(specification)과 단위(unit) 조회
|
||||
*
|
||||
* @param string $itemCode 품목 코드
|
||||
* @return array ['specification' => string|null, 'unit' => string]
|
||||
*/
|
||||
private function getItemSpecAndUnit(string $itemCode): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
if (! $tenantId) {
|
||||
return ['specification' => null, 'unit' => 'EA'];
|
||||
}
|
||||
|
||||
// Items 테이블에서 기본 정보 조회
|
||||
$item = Item::where('tenant_id', $tenantId)
|
||||
->where('code', $itemCode)
|
||||
->whereNull('deleted_at')
|
||||
->with('details')
|
||||
->first();
|
||||
|
||||
if (! $item) {
|
||||
return ['specification' => null, 'unit' => 'EA'];
|
||||
}
|
||||
|
||||
// specification은 ItemDetail에서, unit은 Item에서 가져옴
|
||||
$specification = $item->details?->specification ?? null;
|
||||
$unit = $item->unit ?? 'EA';
|
||||
|
||||
return [
|
||||
'specification' => $specification,
|
||||
'unit' => $unit,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM 전개 (수량 수식 포함)
|
||||
*/
|
||||
@@ -1102,18 +1145,22 @@ private function expandBomWithFormulas(array $finishedGoods, array $variables, i
|
||||
$quantityFormula = $bomEntry['quantityFormula'] ?? $bomEntry['quantity'] ?? '1';
|
||||
|
||||
if ($childItemId) {
|
||||
// ID 기반 조회
|
||||
// ID 기반 조회 (item_details 조인하여 specification 포함)
|
||||
$childItem = DB::table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('id', $childItemId)
|
||||
->whereNull('deleted_at')
|
||||
->leftJoin('item_details', 'items.id', '=', 'item_details.item_id')
|
||||
->where('items.tenant_id', $tenantId)
|
||||
->where('items.id', $childItemId)
|
||||
->whereNull('items.deleted_at')
|
||||
->select('items.*', 'item_details.specification')
|
||||
->first();
|
||||
} elseif ($childItemCode) {
|
||||
// 코드 기반 조회
|
||||
// 코드 기반 조회 (item_details 조인하여 specification 포함)
|
||||
$childItem = DB::table('items')
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', $childItemCode)
|
||||
->whereNull('deleted_at')
|
||||
->leftJoin('item_details', 'items.id', '=', 'item_details.item_id')
|
||||
->where('items.tenant_id', $tenantId)
|
||||
->where('items.code', $childItemCode)
|
||||
->whereNull('items.deleted_at')
|
||||
->select('items.*', 'item_details.specification')
|
||||
->first();
|
||||
} else {
|
||||
continue;
|
||||
@@ -1127,6 +1174,7 @@ private function expandBomWithFormulas(array $finishedGoods, array $variables, i
|
||||
'process_type' => $childItem->process_type,
|
||||
'quantity_formula' => (string) $quantityFormula,
|
||||
'unit' => $childItem->unit,
|
||||
'specification' => $childItem->specification,
|
||||
];
|
||||
|
||||
// 재귀적 BOM 전개 (반제품인 경우)
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
class QuoteService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private QuoteNumberService $numberService
|
||||
private QuoteNumberService $numberService,
|
||||
private QuoteCalculationService $calculationService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -90,9 +91,79 @@ public function show(int $id): Quote
|
||||
throw new NotFoundHttpException(__('error.quote_not_found'));
|
||||
}
|
||||
|
||||
// BOM 자재 데이터 계산 및 추가
|
||||
$bomMaterials = $this->calculateBomMaterials($quote);
|
||||
if (! empty($bomMaterials)) {
|
||||
$quote->setAttribute('bom_materials', $bomMaterials);
|
||||
}
|
||||
|
||||
return $quote;
|
||||
}
|
||||
|
||||
/**
|
||||
* 저장된 calculation_inputs를 기반으로 BOM 자재 목록 계산
|
||||
*/
|
||||
private function calculateBomMaterials(Quote $quote): array
|
||||
{
|
||||
$calculationInputs = $quote->calculation_inputs;
|
||||
|
||||
// calculation_inputs가 없거나 items가 없으면 빈 배열 반환
|
||||
if (empty($calculationInputs) || empty($calculationInputs['items'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$inputItems = $calculationInputs['items'];
|
||||
$allMaterials = [];
|
||||
|
||||
foreach ($inputItems as $index => $input) {
|
||||
// 완제품 코드 찾기 (productName에 저장됨)
|
||||
$finishedGoodsCode = $input['productName'] ?? null;
|
||||
if (! $finishedGoodsCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// BOM 계산을 위한 입력 변수 구성
|
||||
$bomInputs = [
|
||||
'W0' => (float) ($input['openWidth'] ?? 0),
|
||||
'H0' => (float) ($input['openHeight'] ?? 0),
|
||||
'QTY' => (float) ($input['quantity'] ?? 1),
|
||||
'PC' => $input['productCategory'] ?? 'SCREEN',
|
||||
'GT' => $input['guideRailType'] ?? 'wall',
|
||||
'MP' => $input['motorPower'] ?? 'single',
|
||||
'CT' => $input['controller'] ?? 'basic',
|
||||
'WS' => (float) ($input['wingSize'] ?? 50),
|
||||
'INSP' => (float) ($input['inspectionFee'] ?? 50000),
|
||||
];
|
||||
|
||||
try {
|
||||
$result = $this->calculationService->calculateBom($finishedGoodsCode, $bomInputs, false);
|
||||
|
||||
if (($result['success'] ?? false) && ! empty($result['items'])) {
|
||||
// 각 자재 항목에 인덱스 정보 추가
|
||||
foreach ($result['items'] as $material) {
|
||||
$allMaterials[] = [
|
||||
'item_index' => $index,
|
||||
'finished_goods_code' => $finishedGoodsCode,
|
||||
'item_code' => $material['item_code'] ?? '',
|
||||
'item_name' => $material['item_name'] ?? '',
|
||||
'specification' => $material['specification'] ?? '',
|
||||
'unit' => $material['unit'] ?? 'EA',
|
||||
'quantity' => $material['quantity'] ?? 0,
|
||||
'unit_price' => $material['unit_price'] ?? 0,
|
||||
'total_price' => $material['total_price'] ?? 0,
|
||||
'formula_category' => $material['formula_category'] ?? '',
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// BOM 계산 실패 시 해당 품목은 스킵
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $allMaterials;
|
||||
}
|
||||
|
||||
/**
|
||||
* 견적 생성
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user