From 6dbcb5337d7cda7763ec8789d9093e1ab6011406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Fri, 6 Feb 2026 19:40:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[=EC=88=98=EC=A3=BC=EA=B4=80=EB=A6=AC]?= =?UTF-8?q?=20convertToOrder=20=EA=B0=9C=EC=86=8C=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - convertToOrder에서 calculation_inputs.items[] 파싱하여 floor_code/symbol_code 매핑 - resolveLocationMapping() 공통 메소드 추출 (note 파싱 1순위, formula_source 2순위) - syncFromQuote와 동일한 2단계 파싱 로직으로 일관성 확보 - Exception → Throwable 변경 (동기화 실패 catch 범위 확대) Co-Authored-By: Claude Opus 4.6 --- app/Services/Quote/QuoteService.php | 49 +++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index f0d076a..a27fddc 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -448,7 +448,7 @@ public function update(int $id, array $data): Quote try { $this->orderService->setContext($tenantId, $userId); $this->orderService->syncFromQuote($quote, $quote->current_revision); - } catch (\Exception $e) { + } catch (\Throwable $e) { // 수주 동기화 실패는 로그만 남기고 견적 수정은 성공 처리 Log::warning('Failed to sync order from quote', [ 'quote_id' => $quote->id, @@ -597,10 +597,14 @@ public function convertToOrder(int $id): Quote $order->created_by = $userId; $order->save(); - // 수주 상세 품목 생성 + // 수주 상세 품목 생성 (개소 매핑 포함) + $calculationInputs = $quote->calculation_inputs ?? []; + $productItems = $calculationInputs['items'] ?? []; + $serialIndex = 1; foreach ($quote->items as $quoteItem) { - $orderItem = OrderItem::createFromQuoteItem($quoteItem, $order->id, $serialIndex); + $productMapping = $this->resolveLocationMapping($quoteItem, $productItems); + $orderItem = OrderItem::createFromQuoteItem($quoteItem, $order->id, $serialIndex, $productMapping); $orderItem->created_by = $userId; $orderItem->save(); $serialIndex++; @@ -622,6 +626,45 @@ public function convertToOrder(int $id): Quote }); } + /** + * 견적 품목에서 개소(층/부호) 매핑 정보 추출 + * + * 1순위: note 필드 파싱 ("4F FSS-01" → floor_code:"4F", symbol_code:"FSS-01") + * 2순위: formula_source → calculation_inputs.items[] 매칭 + */ + private function resolveLocationMapping(QuoteItem $quoteItem, array $productItems): array + { + $floorCode = null; + $symbolCode = null; + + // 1순위: note에서 파싱 + $note = trim($quoteItem->note ?? ''); + if ($note !== '') { + $parts = preg_split('/\s+/', $note, 2); + $floorCode = $parts[0] ?? null; + $symbolCode = $parts[1] ?? null; + } + + // 2순위: formula_source → calculation_inputs + if (empty($floorCode) && empty($symbolCode)) { + $productIndex = 0; + $formulaSource = $quoteItem->formula_source ?? ''; + if (preg_match('/product_(\d+)/', $formulaSource, $matches)) { + $productIndex = (int) $matches[1]; + } + + if (isset($productItems[$productIndex])) { + $floorCode = $productItems[$productIndex]['floor'] ?? null; + $symbolCode = $productItems[$productIndex]['code'] ?? null; + } elseif (count($productItems) === 1) { + $floorCode = $productItems[0]['floor'] ?? null; + $symbolCode = $productItems[0]['code'] ?? null; + } + } + + return ['floor_code' => $floorCode, 'symbol_code' => $symbolCode]; + } + /** * 수주번호 생성 * 형식: ORD-YYMMDD-NNN (예: ORD-260105-001)