feat: [수주관리] 전환/동기화 로직에 OrderNode 생성 및 아이템 연결
- convertToOrder: calculation_inputs.items[]로 OrderNode(location) 생성, order_items에 order_node_id 연결 - resolveLocationIndex() 헬퍼 추가 (formula_source/note 기반 개소 인덱스 매칭) - syncFromQuote: 기존 nodes 삭제 후 재생성, 아이템 node 연결 동기화 - show(): rootNodes + withRecursiveChildren eager loading 추가 - createFromQuoteItem: order_node_id 매핑 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
use App\Models\Items\Item;
|
||||
use App\Models\Orders\Order;
|
||||
use App\Models\Orders\OrderItem;
|
||||
use App\Models\Orders\OrderNode;
|
||||
use App\Models\Quote\Quote;
|
||||
use App\Models\Quote\QuoteItem;
|
||||
use App\Models\Quote\QuoteRevision;
|
||||
@@ -597,13 +598,60 @@ public function convertToOrder(int $id): Quote
|
||||
$order->created_by = $userId;
|
||||
$order->save();
|
||||
|
||||
// 수주 상세 품목 생성 (개소 매핑 포함)
|
||||
// calculation_inputs에서 개소(제품) 정보 추출
|
||||
$calculationInputs = $quote->calculation_inputs ?? [];
|
||||
$productItems = $calculationInputs['items'] ?? [];
|
||||
$bomResults = $calculationInputs['bomResults'] ?? [];
|
||||
|
||||
// OrderNode 생성 (개소별)
|
||||
$nodeMap = []; // productIndex → OrderNode
|
||||
foreach ($productItems as $idx => $locItem) {
|
||||
$bomResult = $bomResults[$idx] ?? null;
|
||||
$grandTotal = $bomResult['grand_total'] ?? 0;
|
||||
$qty = (int) ($locItem['quantity'] ?? 1);
|
||||
$floor = $locItem['floor'] ?? '';
|
||||
$symbol = $locItem['code'] ?? '';
|
||||
|
||||
$node = OrderNode::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'order_id' => $order->id,
|
||||
'parent_id' => null,
|
||||
'node_type' => 'location',
|
||||
'code' => trim("{$floor}-{$symbol}", '-') ?: "LOC-{$idx}",
|
||||
'name' => trim("{$floor} {$symbol}") ?: '개소 '.($idx + 1),
|
||||
'status_code' => OrderNode::STATUS_PENDING,
|
||||
'quantity' => $qty,
|
||||
'unit_price' => $grandTotal,
|
||||
'total_price' => $grandTotal * $qty,
|
||||
'options' => [
|
||||
'floor' => $floor,
|
||||
'symbol' => $symbol,
|
||||
'product_code' => $locItem['productCode'] ?? null,
|
||||
'product_name' => $locItem['productName'] ?? null,
|
||||
'open_width' => $locItem['openWidth'] ?? null,
|
||||
'open_height' => $locItem['openHeight'] ?? null,
|
||||
'guide_rail_type' => $locItem['guideRailType'] ?? null,
|
||||
'motor_power' => $locItem['motorPower'] ?? null,
|
||||
'controller' => $locItem['controller'] ?? null,
|
||||
'wing_size' => $locItem['wingSize'] ?? null,
|
||||
'inspection_fee' => $locItem['inspectionFee'] ?? null,
|
||||
'bom_result' => $bomResult,
|
||||
],
|
||||
'depth' => 0,
|
||||
'sort_order' => $idx,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$nodeMap[$idx] = $node;
|
||||
}
|
||||
|
||||
// 수주 상세 품목 생성 (노드 연결 포함)
|
||||
$serialIndex = 1;
|
||||
foreach ($quote->items as $quoteItem) {
|
||||
$productMapping = $this->resolveLocationMapping($quoteItem, $productItems);
|
||||
$locIdx = $this->resolveLocationIndex($quoteItem, $productItems);
|
||||
|
||||
$productMapping['order_node_id'] = isset($nodeMap[$locIdx]) ? $nodeMap[$locIdx]->id : null;
|
||||
|
||||
$orderItem = OrderItem::createFromQuoteItem($quoteItem, $order->id, $serialIndex, $productMapping);
|
||||
$orderItem->created_by = $userId;
|
||||
$orderItem->save();
|
||||
@@ -665,6 +713,37 @@ private function resolveLocationMapping(QuoteItem $quoteItem, array $productItem
|
||||
return ['floor_code' => $floorCode, 'symbol_code' => $symbolCode];
|
||||
}
|
||||
|
||||
/**
|
||||
* 견적 품목이 속하는 개소(productItems) 인덱스 반환
|
||||
*
|
||||
* 1순위: formula_source에서 product_N 패턴 추출
|
||||
* 2순위: note 파싱 후 productItems에서 floor/code 매칭
|
||||
* 매칭 실패 시 0 반환
|
||||
*/
|
||||
private function resolveLocationIndex(QuoteItem $quoteItem, array $productItems): int
|
||||
{
|
||||
// 1순위: formula_source
|
||||
$formulaSource = $quoteItem->formula_source ?? '';
|
||||
if (preg_match('/product_(\d+)/', $formulaSource, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
// 2순위: note에서 floor/code 매칭
|
||||
$note = trim($quoteItem->note ?? '');
|
||||
if ($note !== '') {
|
||||
$parts = preg_split('/\s+/', $note, 2);
|
||||
$floor = $parts[0] ?? '';
|
||||
$code = $parts[1] ?? '';
|
||||
foreach ($productItems as $idx => $item) {
|
||||
if (($item['floor'] ?? '') === $floor && ($item['code'] ?? '') === $code) {
|
||||
return $idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 수주번호 생성
|
||||
* 형식: ORD-YYMMDD-NNN (예: ORD-260105-001)
|
||||
|
||||
Reference in New Issue
Block a user