fix: [stock] 재고생산 삭제 허용 (IN_PROGRESS + 작업지시 함께 정리)

- STOCK 타입: IN_PROGRESS 상태에서도 삭제 허용 (IN_PRODUCTION부터 불가)
- STOCK 타입: 작업지시(workOrders)가 있어도 삭제 허용
- cleanupStockWorkOrders(): 작업지시 soft delete + 재고예약 해제
- destroy(), bulkDestroy() 양쪽 적용
- 일반 수주(ORDER)는 기존 규칙 유지
This commit is contained in:
김보곤
2026-03-18 22:20:59 +09:00
parent 8f8eae92f2
commit d8b8df6f47

View File

@@ -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)