fix: 수주 삭제 시 견적 연결 해제 + 생산지시 공정 매핑 보완

- OrderService::destroy()에서 견적 order_id/status 초기화
- StoreOrderRequest/UpdateOrderRequest에 floor_code, symbol_code, item_code 추가
- createProductionOrder()에 item_code fallback 공정 매핑 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 19:03:22 +09:00
parent 6733a431bb
commit 2d68e5e669
3 changed files with 59 additions and 0 deletions

View File

@@ -58,6 +58,7 @@ public function rules(): array
// 품목 배열
'items' => 'nullable|array',
'items.*.item_id' => 'nullable|integer|exists:items,id',
'items.*.item_code' => 'nullable|string|max:50',
'items.*.item_name' => 'required|string|max:200',
'items.*.specification' => 'nullable|string|max:500',
'items.*.quantity' => 'required|numeric|min:0',
@@ -66,6 +67,8 @@ public function rules(): array
'items.*.supply_amount' => 'nullable|numeric|min:0',
'items.*.tax_amount' => 'nullable|numeric|min:0',
'items.*.total_amount' => 'nullable|numeric|min:0',
'items.*.floor_code' => 'nullable|string|max:50',
'items.*.symbol_code' => 'nullable|string|max:50',
];
}

View File

@@ -52,6 +52,7 @@ public function rules(): array
// 품목 배열 (전체 교체)
'items' => 'nullable|array',
'items.*.item_id' => 'nullable|integer|exists:items,id',
'items.*.item_code' => 'nullable|string|max:50',
'items.*.item_name' => 'required|string|max:200',
'items.*.specification' => 'nullable|string|max:500',
'items.*.quantity' => 'required|numeric|min:0',
@@ -60,6 +61,8 @@ public function rules(): array
'items.*.supply_amount' => 'nullable|numeric|min:0',
'items.*.tax_amount' => 'nullable|numeric|min:0',
'items.*.total_amount' => 'nullable|numeric|min:0',
'items.*.floor_code' => 'nullable|string|max:50',
'items.*.symbol_code' => 'nullable|string|max:50',
];
}

View File

@@ -284,6 +284,17 @@ public function destroy(int $id)
}
return DB::transaction(function () use ($order, $userId) {
// 0. 연결된 견적의 수주 연결 해제 (order_id → null, status → finalized)
if ($order->quote_id) {
Quote::withoutGlobalScopes()
->where('id', $order->quote_id)
->where('order_id', $order->id)
->update([
'order_id' => null,
'status' => Quote::STATUS_FINALIZED,
]);
}
// 1. order_item_components soft delete
foreach ($order->items as $item) {
$item->components()->update(['deleted_by' => $userId]);
@@ -872,6 +883,20 @@ public function createProductionOrder(int $orderId, array $data)
}
}
// item_code → item_id 매핑 구축 (fallback용)
$codeToIdMap = [];
if (! empty($bomItemIds)) {
$codeToIdRows = DB::table('items')
->where('tenant_id', $tenantId)
->whereIn('id', $bomItemIds)
->whereNull('deleted_at')
->select('id', 'code')
->get();
foreach ($codeToIdRows as $row) {
$codeToIdMap[$row->code] = $row->id;
}
}
// order_items를 공정별로 그룹화 (BOM item_id → process 매핑 활용)
$itemsByProcess = [];
foreach ($order->items as $orderItem) {
@@ -890,6 +915,34 @@ public function createProductionOrder(int $orderId, array $data)
}
}
// 3. fallback: item_code로 items 마스터 조회 → process_items 매핑
if ($processId === null && $orderItem->item_code) {
$resolvedId = $codeToIdMap[$orderItem->item_code] ?? null;
if (! $resolvedId) {
$resolvedId = DB::table('items')
->where('tenant_id', $tenantId)
->where('code', $orderItem->item_code)
->whereNull('deleted_at')
->value('id');
if ($resolvedId) {
$codeToIdMap[$orderItem->item_code] = $resolvedId;
}
}
if ($resolvedId && isset($itemProcessMap[$resolvedId])) {
$processId = $itemProcessMap[$resolvedId];
} elseif ($resolvedId) {
// process_items에서도 조회
$pi = DB::table('process_items')
->where('item_id', $resolvedId)
->where('is_active', true)
->value('process_id');
if ($pi) {
$processId = $pi;
$itemProcessMap[$resolvedId] = $pi;
}
}
}
$key = $processId ?? 'none';
if (! isset($itemsByProcess[$key])) {