From 6bc766411ba6a2c0a1445dc38a0b25f312760f94 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 10:28:30 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=83=9D=EC=82=B0=EC=A7=80=EC=8B=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EA=B3=B5=EC=A0=95=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EB=B6=84=EB=A5=98=20=EB=B0=8F=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OrderService: 생산지시 생성 로직 개선 - order_items.item_id → process_items 테이블에서 공정 자동 조회 - 공정별로 아이템 그룹화 (미지정 아이템은 별도 그룹) - 각 공정별 작업지시 생성 - work_order_items에 해당 공정의 아이템들 자동 추가 - WorkOrderService: 목록 조회 시 관계 추가 - items 관계 추가 (틀수 계산용) - process.department 필드 추가 (부서 표시용) Co-Authored-By: Claude --- app/Services/OrderService.php | 103 +++++++++++++++++++++--------- app/Services/WorkOrderService.php | 16 +++-- 2 files changed, 86 insertions(+), 33 deletions(-) diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index e85fb53..00e3b52 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -612,29 +612,29 @@ public function syncFromQuote(Quote $quote, int $revision): ?Order $floorCode = null; $symbolCode = null; - // formula_source에서 제품 인덱스 추출 - $productIndex = 0; - $formulaSource = $quoteItem->formula_source ?? ''; - if (preg_match('/product_(\d+)/', $formulaSource, $matches)) { - $productIndex = (int) $matches[1]; + // 1순위: note에서 floor/code 파싱 (가장 정확한 정보) + // note 형식: "4F FSS-01" (공백으로 구분) + $note = trim($quoteItem->note ?? ''); + if ($note !== '') { + $parts = preg_split('/\s+/', $note, 2); + $floorCode = $parts[0] ?? null; + $symbolCode = $parts[1] ?? null; } - // calculation_inputs에서 floor/code 가져오기 - 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; - } - - // note에서 파싱 시도 + // 2순위: formula_source에서 제품 인덱스 추출하여 calculation_inputs에서 가져오기 if (empty($floorCode) && empty($symbolCode)) { - $note = trim($quoteItem->note ?? ''); - if ($note !== '') { - $parts = preg_split('/\s+/', $note, 2); - $floorCode = $parts[0] ?? null; - $symbolCode = $parts[1] ?? null; + $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; } } @@ -721,21 +721,47 @@ public function createProductionOrder(int $orderId, array $data) throw new BadRequestHttpException(__('error.order.production_order_already_exists')); } - // process_ids 배열 또는 단일 process_id 처리 - $processIds = $data['process_ids'] ?? []; - if (empty($processIds) && ! empty($data['process_id'])) { - $processIds = [$data['process_id']]; + // order_items의 item_id를 기반으로 공정별 자동 분류 + $itemIds = $order->items->pluck('item_id')->filter()->unique()->values()->toArray(); + + // process_items 테이블에서 item_id → process_id 매핑 조회 + $itemProcessMap = []; + if (! empty($itemIds)) { + $processItems = DB::table('process_items as pi') + ->join('processes as p', 'pi.process_id', '=', 'p.id') + ->where('p.tenant_id', $tenantId) + ->whereIn('pi.item_id', $itemIds) + ->where('pi.is_active', true) + ->select('pi.item_id', 'pi.process_id') + ->get(); + + foreach ($processItems as $pi) { + $itemProcessMap[$pi->item_id] = $pi->process_id; + } } - // 공정이 없으면 null로 하나만 생성 - if (empty($processIds)) { - $processIds = [null]; + // order_items를 공정별로 그룹화 + $itemsByProcess = []; + foreach ($order->items as $orderItem) { + $processId = $itemProcessMap[$orderItem->item_id] ?? null; + $key = $processId ?? 'none'; // null은 'none' 키로 그룹화 + + if (! isset($itemsByProcess[$key])) { + $itemsByProcess[$key] = [ + 'process_id' => $processId, + 'items' => [], + ]; + } + $itemsByProcess[$key]['items'][] = $orderItem; } - return DB::transaction(function () use ($order, $data, $tenantId, $userId, $processIds) { + return DB::transaction(function () use ($order, $data, $tenantId, $userId, $itemsByProcess) { $workOrders = []; - foreach ($processIds as $processId) { + foreach ($itemsByProcess as $key => $group) { + $processId = $group['process_id']; + $items = $group['items']; + // 작업지시번호 생성 $workOrderNo = $this->generateWorkOrderNo($tenantId); @@ -756,6 +782,25 @@ public function createProductionOrder(int $orderId, array $data) 'updated_by' => $userId, ]); + // work_order_items에 아이템 추가 + $sortOrder = 1; + foreach ($items as $orderItem) { + DB::table('work_order_items')->insert([ + 'tenant_id' => $tenantId, + 'work_order_id' => $workOrder->id, + 'source_order_item_id' => $orderItem->id, + 'item_id' => $orderItem->item_id, + 'item_name' => $orderItem->item_name, + 'specification' => $orderItem->specification, + 'quantity' => $orderItem->quantity, + 'unit' => $orderItem->unit, + 'sort_order' => $sortOrder++, + 'status' => 'pending', + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + $workOrders[] = $workOrder->load(['assignee:id,name', 'team:id,name', 'process:id,process_name,process_code']); } diff --git a/app/Services/WorkOrderService.php b/app/Services/WorkOrderService.php index ecbfbe6..68191de 100644 --- a/app/Services/WorkOrderService.php +++ b/app/Services/WorkOrderService.php @@ -49,7 +49,8 @@ public function index(array $params) 'team:id,name', 'salesOrder:id,order_no,client_id,client_name', 'salesOrder.client:id,name', - 'process:id,process_name,process_code', + 'process:id,process_name,process_code,department', + 'items:id,work_order_id,item_name,quantity', ]); // 검색어 @@ -66,8 +67,14 @@ public function index(array $params) } // 공정 필터 (process_id) + // - 'none' 또는 '0': 공정 미지정 (process_id IS NULL) + // - 숫자: 해당 공정 ID로 필터 if ($processId !== null) { - $query->where('process_id', $processId); + if ($processId === 'none' || $processId === '0' || $processId === 0) { + $query->whereNull('process_id'); + } else { + $query->where('process_id', $processId); + } } // 공정 코드 필터 (process_code) - 대시보드용 @@ -143,9 +150,10 @@ public function show(int $id) 'assignee:id,name', 'assignees.user:id,name', 'team:id,name', - 'salesOrder:id,order_no,site_name,client_id', + 'salesOrder:id,order_no,site_name,client_id,client_contact,received_at,writer_id,created_at,quantity', 'salesOrder.client:id,name', - 'process:id,process_name,process_code,work_steps', + 'salesOrder.writer:id,name', + 'process:id,process_name,process_code,work_steps,department', 'items', 'bendingDetail', 'issues' => fn ($q) => $q->orderByDesc('created_at'),