From d8b8df6f475c9b70281b37d01c3d690474bda3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Wed, 18 Mar 2026 22:20:59 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[stock]=20=EC=9E=AC=EA=B3=A0=EC=83=9D?= =?UTF-8?q?=EC=82=B0=20=EC=82=AD=EC=A0=9C=20=ED=97=88=EC=9A=A9=20(IN=5FPRO?= =?UTF-8?q?GRESS=20+=20=EC=9E=91=EC=97=85=EC=A7=80=EC=8B=9C=20=ED=95=A8?= =?UTF-8?q?=EA=BB=98=20=EC=A0=95=EB=A6=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - STOCK 타입: IN_PROGRESS 상태에서도 삭제 허용 (IN_PRODUCTION부터 불가) - STOCK 타입: 작업지시(workOrders)가 있어도 삭제 허용 - cleanupStockWorkOrders(): 작업지시 soft delete + 재고예약 해제 - destroy(), bulkDestroy() 양쪽 적용 - 일반 수주(ORDER)는 기존 규칙 유지 --- app/Services/OrderService.php | 131 ++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 23 deletions(-) diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index 254df9a8..03963f26 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -426,21 +426,38 @@ public function destroy(int $id) throw new NotFoundHttpException(__('error.not_found')); } - // 진행 중이거나 완료된 수주는 삭제 불가 - if (in_array($order->status_code, [ - Order::STATUS_IN_PROGRESS, - Order::STATUS_IN_PRODUCTION, - Order::STATUS_PRODUCED, - Order::STATUS_SHIPPING, - Order::STATUS_SHIPPED, - Order::STATUS_COMPLETED, - ])) { - throw new BadRequestHttpException(__('error.order.cannot_delete_in_progress')); + $isStock = $order->order_type_code === Order::TYPE_STOCK; + + // 재고생산(STOCK): 실제 생산 시작(IN_PRODUCTION) 전까지 삭제 허용 + // 일반 수주: 기존 규칙 유지 + if ($isStock) { + if (in_array($order->status_code, [ + Order::STATUS_IN_PRODUCTION, + Order::STATUS_PRODUCED, + Order::STATUS_SHIPPING, + Order::STATUS_SHIPPED, + Order::STATUS_COMPLETED, + ])) { + throw new BadRequestHttpException(__('error.order.cannot_delete_in_progress')); + } + } else { + if (in_array($order->status_code, [ + Order::STATUS_IN_PROGRESS, + Order::STATUS_IN_PRODUCTION, + Order::STATUS_PRODUCED, + Order::STATUS_SHIPPING, + Order::STATUS_SHIPPED, + Order::STATUS_COMPLETED, + ])) { + throw new BadRequestHttpException(__('error.order.cannot_delete_in_progress')); + } } - // 작업지시가 존재하면 삭제 불가 - if ($order->workOrders()->exists()) { - throw new BadRequestHttpException(__('error.order.cannot_delete_has_work_orders')); + // 재고생산(STOCK): 작업지시가 있어도 삭제 허용 (함께 정리) + if (! $isStock) { + if ($order->workOrders()->exists()) { + throw new BadRequestHttpException(__('error.order.cannot_delete_has_work_orders')); + } } // 출하 정보가 존재하면 삭제 불가 @@ -448,7 +465,12 @@ public function destroy(int $id) throw new BadRequestHttpException(__('error.order.cannot_delete_has_shipments')); } - return DB::transaction(function () use ($order, $userId) { + return DB::transaction(function () use ($order, $userId, $isStock) { + // 재고생산(STOCK): 연관 작업지시 + 재고예약 정리 + if ($isStock && $order->workOrders()->exists()) { + $this->cleanupStockWorkOrders($order, $userId); + } + // 0. 연결된 견적의 수주 연결 해제 (order_id → null, status → finalized) if ($order->quote_id) { Quote::withoutGlobalScopes() @@ -511,18 +533,36 @@ public function bulkDestroy(array $ids, bool $force = false): array // force=true (개발환경 완전삭제): 모든 상태 허용, 연관 데이터 모두 삭제 $this->forceDeleteWorkOrders($order, $tenantId); } else { - // 일반 삭제: 상태/작업지시/출하 검증 - if (! in_array($order->status_code, [ - Order::STATUS_DRAFT, - Order::STATUS_CONFIRMED, - Order::STATUS_CANCELLED, - ])) { - $skippedIds[] = $order->id; + $isStock = $order->order_type_code === Order::TYPE_STOCK; - continue; + // 재고생산(STOCK): IN_PROGRESS까지 삭제 허용 (IN_PRODUCTION부터 불가) + // 일반 수주: DRAFT/CONFIRMED/CANCELLED만 삭제 허용 + if ($isStock) { + if (in_array($order->status_code, [ + Order::STATUS_IN_PRODUCTION, + Order::STATUS_PRODUCED, + Order::STATUS_SHIPPING, + Order::STATUS_SHIPPED, + Order::STATUS_COMPLETED, + ])) { + $skippedIds[] = $order->id; + + continue; + } + } else { + if (! in_array($order->status_code, [ + Order::STATUS_DRAFT, + Order::STATUS_CONFIRMED, + Order::STATUS_CANCELLED, + ])) { + $skippedIds[] = $order->id; + + continue; + } } - if ($order->workOrders()->exists()) { + // 재고생산(STOCK): 작업지시가 있어도 삭제 허용 (함께 정리) + if (! $isStock && $order->workOrders()->exists()) { $skippedIds[] = $order->id; continue; @@ -533,6 +573,11 @@ public function bulkDestroy(array $ids, bool $force = false): array continue; } + + // 재고생산(STOCK): 연관 작업지시 + 재고예약 정리 + if ($isStock && $order->workOrders()->exists()) { + $this->cleanupStockWorkOrders($order, $userId); + } } // 견적 연결 해제 @@ -583,6 +628,46 @@ public function bulkDestroy(array $ids, bool $force = false): array /** * 작업지시 및 연관 데이터 강제 삭제 (개발환경 완전삭제용) */ + /** + * 재고생산(STOCK) 삭제 시 연관 작업지시 + 재고예약 정리 + * IN_PROGRESS 상태(아직 자재투입 없음)의 작업지시만 soft delete 처리 + */ + private function cleanupStockWorkOrders(Order $order, int $userId): void + { + $workOrders = $order->workOrders()->get(); + + foreach ($workOrders as $wo) { + // 자재투입이 있는 작업지시는 정리 불가 (이미 생산 시작됨) + if (WorkOrderMaterialInput::where('work_order_id', $wo->id)->exists()) { + Log::warning('재고생산 삭제: 자재투입된 작업지시 건너뜀', [ + 'order_id' => $order->id, + 'work_order_id' => $wo->id, + ]); + + continue; + } + + // 작업지시 품목 soft delete + $wo->items()->update(['deleted_by' => $userId]); + $wo->items()->delete(); + + // 작업지시 soft delete + $wo->deleted_by = $userId; + $wo->save(); + $wo->delete(); + } + + // 재고예약 해제 + try { + app(StockService::class)->releaseReservationForOrder($order->items, $order->id); + } catch (\Exception $e) { + Log::warning('재고생산 삭제: 재고예약 해제 실패', [ + 'order_id' => $order->id, + 'error' => $e->getMessage(), + ]); + } + } + private function forceDeleteWorkOrders(Order $order, int $tenantId): void { $workOrderIds = WorkOrder::where('tenant_id', $tenantId)