From ba49313ffa98e0095cf5a46f17766515dee575e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Sat, 21 Feb 2026 09:49:00 +0900 Subject: [PATCH] =?UTF-8?q?fix(WEB):=20=EC=88=98=EC=A3=BC=20=EC=99=84?= =?UTF-8?q?=EC=A0=84=EC=82=AD=EC=A0=9C(force)=20=EC=8B=9C=20=EC=83=9D?= =?UTF-8?q?=EC=82=B0=EC=A7=80=EC=8B=9C=EC=99=84=EB=A3=8C=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20skip=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bulkDestroy force=true일 때 상태 체크 bypass, 연관 작업지시 데이터 모두 삭제 - forceDeleteWorkOrders() 헬퍼: 자재투입 재고복구, 문서, 부속데이터 정리 후 hard delete Co-Authored-By: Claude Opus 4.6 --- app/Services/OrderService.php | 123 ++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 19 deletions(-) diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index a040d13..70fd65e 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -428,31 +428,34 @@ public function bulkDestroy(array $ids, bool $force = false): array $deletedCount = 0; $skippedIds = []; - return DB::transaction(function () use ($orders, $force, $userId, &$deletedCount, &$skippedIds) { + return DB::transaction(function () use ($orders, $force, $userId, $tenantId, &$deletedCount, &$skippedIds) { foreach ($orders as $order) { - // 상태 검증: DRAFT/CONFIRMED/CANCELLED만 삭제 가능 - if (! in_array($order->status_code, [ - Order::STATUS_DRAFT, - Order::STATUS_CONFIRMED, - Order::STATUS_CANCELLED, - ])) { - $skippedIds[] = $order->id; + if ($force) { + // 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; - continue; - } + continue; + } - // 작업지시 존재 시 skip - if ($order->workOrders()->exists()) { - $skippedIds[] = $order->id; + if ($order->workOrders()->exists()) { + $skippedIds[] = $order->id; - continue; - } + continue; + } - // 출하 존재 시 skip - if ($order->shipments()->exists()) { - $skippedIds[] = $order->id; + if ($order->shipments()->exists()) { + $skippedIds[] = $order->id; - continue; + continue; + } } // 견적 연결 해제 @@ -500,6 +503,72 @@ public function bulkDestroy(array $ids, bool $force = false): array }); } + /** + * 작업지시 및 연관 데이터 강제 삭제 (개발환경 완전삭제용) + */ + private function forceDeleteWorkOrders(Order $order, int $tenantId): void + { + $workOrderIds = WorkOrder::where('tenant_id', $tenantId) + ->where('sales_order_id', $order->id) + ->pluck('id') + ->toArray(); + + if (empty($workOrderIds)) { + return; + } + + // 1. 자재 투입 재고 복구 + 삭제 + $materialInputs = WorkOrderMaterialInput::whereIn('work_order_id', $workOrderIds)->get(); + if ($materialInputs->isNotEmpty()) { + $stockService = app(StockService::class); + foreach ($materialInputs as $input) { + try { + $stockService->increaseToLot( + stockLotId: $input->stock_lot_id, + qty: (float) $input->qty, + reason: 'work_order_input_cancel', + referenceId: $input->work_order_id + ); + } catch (\Exception $e) { + Log::warning('완전삭제: 재고 복원 실패', [ + 'input_id' => $input->id, + 'stock_lot_id' => $input->stock_lot_id, + 'error' => $e->getMessage(), + ]); + } + } + WorkOrderMaterialInput::whereIn('work_order_id', $workOrderIds)->delete(); + } + + // 2. 문서 삭제 + $documentIds = Document::where('linkable_type', 'work_order') + ->whereIn('linkable_id', $workOrderIds) + ->pluck('id') + ->toArray(); + + if (! empty($documentIds)) { + DocumentData::whereIn('document_id', $documentIds)->delete(); + DocumentApproval::whereIn('document_id', $documentIds)->delete(); + Document::whereIn('id', $documentIds)->forceDelete(); + } + + // 3. 출하 참조 해제 + DB::table('shipments') + ->whereIn('work_order_id', $workOrderIds) + ->update(['work_order_id' => null]); + + // 4. 부속 데이터 삭제 + DB::table('work_order_step_progress')->whereIn('work_order_id', $workOrderIds)->delete(); + DB::table('work_order_assignees')->whereIn('work_order_id', $workOrderIds)->delete(); + DB::table('work_order_bending_details')->whereIn('work_order_id', $workOrderIds)->delete(); + DB::table('work_order_issues')->whereIn('work_order_id', $workOrderIds)->delete(); + DB::table('work_results')->whereIn('work_order_id', $workOrderIds)->delete(); + + // 5. 작업지시 품목 → 작업지시 삭제 + DB::table('work_order_items')->whereIn('work_order_id', $workOrderIds)->delete(); + WorkOrder::whereIn('id', $workOrderIds)->forceDelete(); + } + /** * 상태 변경 */ @@ -783,6 +852,17 @@ public function createFromQuote(int $quoteId, array $data = []) ? intdiv($quote->items->count(), $locationCount) : 0; + // DEBUG: 분배 로직 디버깅 (임시) + \Log::info('[createFromQuote] Distribution params', [ + 'quoteId' => $quote->id, + 'itemCount' => $quote->items->count(), + 'locationCount' => $locationCount, + 'hasFormulaSource' => $hasFormulaSource, + 'itemsPerLocation' => $itemsPerLocation, + 'collectionKeys_first5' => $quote->items->keys()->take(5)->all(), + 'nodeMapKeys' => array_keys($nodeMap), + ]); + foreach ($quote->items as $index => $quoteItem) { $floorCode = null; $symbolCode = null; @@ -799,6 +879,11 @@ public function createFromQuote(int $quoteId, array $data = []) $locIdx = min(intdiv($index, $itemsPerLocation), $locationCount - 1); } + // DEBUG: 처음 3개와 전환점(17-19) 로깅 (임시) + if ($index < 3 || ($index >= 17 && $index <= 19)) { + \Log::info("[createFromQuote] item idx={$index} locIdx={$locIdx} fs='{$formulaSource}'"); + } + // calculation_inputs에서 floor/code 가져오기 if (isset($productItems[$locIdx])) { $floorCode = $productItems[$locIdx]['floor'] ?? null;