feat: 修正 BOM 單位顯示與完工入庫彈窗 UI 統一規範
This commit is contained in:
@@ -106,23 +106,16 @@ class ProductionOrderController extends Controller
|
||||
{
|
||||
$status = $request->input('status', 'draft');
|
||||
|
||||
$baseRules = [
|
||||
$rules = [
|
||||
'product_id' => 'required',
|
||||
'output_batch_number' => 'required|string|max:50',
|
||||
'status' => 'nullable|in:draft,completed',
|
||||
'warehouse_id' => $status === 'completed' ? 'required' : 'nullable',
|
||||
'output_quantity' => $status === 'completed' ? 'required|numeric|min:0.01' : 'nullable|numeric',
|
||||
'items' => 'nullable|array',
|
||||
'items.*.inventory_id' => $status === 'completed' ? 'required' : 'nullable',
|
||||
'items.*.quantity_used' => $status === 'completed' ? 'required|numeric|min:0.0001' : 'nullable|numeric',
|
||||
];
|
||||
|
||||
$completedRules = [
|
||||
'warehouse_id' => 'required',
|
||||
'output_quantity' => 'required|numeric|min:0.01',
|
||||
'production_date' => 'required|date',
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.inventory_id' => 'required',
|
||||
'items.*.quantity_used' => 'required|numeric|min:0.0001',
|
||||
];
|
||||
|
||||
$rules = $status === 'completed' ? array_merge($baseRules, $completedRules) : $baseRules;
|
||||
|
||||
$validated = $request->validate($rules);
|
||||
|
||||
DB::transaction(function () use ($validated, $request, $status) {
|
||||
@@ -132,12 +125,12 @@ class ProductionOrderController extends Controller
|
||||
'product_id' => $validated['product_id'],
|
||||
'warehouse_id' => $validated['warehouse_id'] ?? null,
|
||||
'output_quantity' => $validated['output_quantity'] ?? 0,
|
||||
'output_batch_number' => $validated['output_batch_number'],
|
||||
'output_batch_number' => $request->output_batch_number, // 建立時改為選填
|
||||
'output_box_count' => $request->output_box_count,
|
||||
'production_date' => $validated['production_date'] ?? now()->toDateString(),
|
||||
'production_date' => $request->production_date,
|
||||
'expiry_date' => $request->expiry_date,
|
||||
'user_id' => auth()->id(),
|
||||
'status' => $status,
|
||||
'status' => ProductionOrder::STATUS_DRAFT, // 一律存為草稿
|
||||
'remark' => $request->remark,
|
||||
]);
|
||||
|
||||
@@ -155,43 +148,12 @@ class ProductionOrderController extends Controller
|
||||
'quantity_used' => $item['quantity_used'] ?? 0,
|
||||
'unit_id' => $item['unit_id'] ?? null,
|
||||
]);
|
||||
|
||||
if ($status === 'completed') {
|
||||
$this->inventoryService->decreaseInventoryQuantity(
|
||||
$item['inventory_id'],
|
||||
$item['quantity_used'],
|
||||
"生產單 #{$productionOrder->code} 耗料",
|
||||
ProductionOrder::class,
|
||||
$productionOrder->id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 成品入庫
|
||||
if ($status === 'completed') {
|
||||
$this->inventoryService->createInventoryRecord([
|
||||
'warehouse_id' => $validated['warehouse_id'],
|
||||
'product_id' => $validated['product_id'],
|
||||
'quantity' => $validated['output_quantity'],
|
||||
'batch_number' => $validated['output_batch_number'],
|
||||
'box_number' => $request->output_box_count,
|
||||
'arrival_date' => $validated['production_date'],
|
||||
'expiry_date' => $request->expiry_date,
|
||||
'reason' => "生產單 #{$productionOrder->code} 成品入庫",
|
||||
'reference_type' => ProductionOrder::class,
|
||||
'reference_id' => $productionOrder->id,
|
||||
]);
|
||||
|
||||
activity()
|
||||
->performedOn($productionOrder)
|
||||
->causedBy(auth()->user())
|
||||
->log('completed');
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('production-orders.index')
|
||||
->with('success', $status === 'completed' ? '生產單已完成並入庫' : '草稿已儲存');
|
||||
->with('success', '生產單草稿已建立');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +166,9 @@ class ProductionOrderController extends Controller
|
||||
if ($productionOrder->product) {
|
||||
$productionOrder->product->base_unit = $this->inventoryService->getUnits()->where('id', $productionOrder->product->base_unit_id)->first();
|
||||
}
|
||||
$productionOrder->warehouse = $this->inventoryService->getWarehouse($productionOrder->warehouse_id);
|
||||
$productionOrder->warehouse = $productionOrder->warehouse_id
|
||||
? $this->inventoryService->getWarehouse($productionOrder->warehouse_id)
|
||||
: null;
|
||||
$productionOrder->user = $this->coreService->getUser($productionOrder->user_id);
|
||||
|
||||
// 手動水和明細資料
|
||||
@@ -214,7 +178,7 @@ class ProductionOrderController extends Controller
|
||||
// 修正: 移除跨模組關聯 sourcePurchaseOrder.vendor
|
||||
$inventories = $this->inventoryService->getInventoriesByIds(
|
||||
$inventoryIds,
|
||||
['product.baseUnit']
|
||||
['product.baseUnit', 'warehouse']
|
||||
)->keyBy('id');
|
||||
|
||||
// 手動載入 Purchase Orders
|
||||
@@ -238,6 +202,7 @@ class ProductionOrderController extends Controller
|
||||
|
||||
return Inertia::render('Production/Show', [
|
||||
'productionOrder' => $productionOrder,
|
||||
'warehouses' => $this->inventoryService->getAllWarehouses(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -308,7 +273,9 @@ class ProductionOrderController extends Controller
|
||||
|
||||
// 基本水和
|
||||
$productionOrder->product = $this->inventoryService->getProduct($productionOrder->product_id);
|
||||
$productionOrder->warehouse = $this->inventoryService->getWarehouse($productionOrder->warehouse_id);
|
||||
$productionOrder->warehouse = $productionOrder->warehouse_id
|
||||
? $this->inventoryService->getWarehouse($productionOrder->warehouse_id)
|
||||
: null;
|
||||
|
||||
// 手動水和明細資料
|
||||
$items = $productionOrder->items;
|
||||
@@ -346,39 +313,27 @@ class ProductionOrderController extends Controller
|
||||
$status = $request->input('status', 'draft');
|
||||
|
||||
// 基礎驗證規則
|
||||
$baseRules = [
|
||||
'product_id' => 'required|exists:products,id',
|
||||
'output_batch_number' => 'required|string|max:50',
|
||||
'status' => 'required|in:draft,completed',
|
||||
$rules = [
|
||||
'product_id' => 'required',
|
||||
'remark' => 'nullable|string',
|
||||
'warehouse_id' => 'nullable',
|
||||
'output_quantity' => 'nullable|numeric',
|
||||
'items' => 'nullable|array',
|
||||
'items.*.inventory_id' => 'required',
|
||||
'items.*.quantity_used' => 'required|numeric',
|
||||
];
|
||||
|
||||
// 完工時的嚴格驗證規則
|
||||
$completedRules = [
|
||||
'warehouse_id' => 'required|exists:warehouses,id',
|
||||
'output_quantity' => 'required|numeric|min:0.01',
|
||||
'production_date' => 'required|date',
|
||||
'expiry_date' => 'nullable|date',
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.inventory_id' => 'required|exists:inventories,id',
|
||||
'items.*.quantity_used' => 'required|numeric|min:0.0001',
|
||||
];
|
||||
|
||||
// 若狀態切換為 completed,需合併驗證規則
|
||||
$rules = $status === 'completed' ? array_merge($baseRules, $completedRules) : $baseRules;
|
||||
|
||||
$validated = $request->validate($rules);
|
||||
|
||||
DB::transaction(function () use ($validated, $request, $status, $productionOrder) {
|
||||
DB::transaction(function () use ($validated, $request, $productionOrder) {
|
||||
$productionOrder->update([
|
||||
'product_id' => $validated['product_id'],
|
||||
'warehouse_id' => $validated['warehouse_id'] ?? $productionOrder->warehouse_id,
|
||||
'output_quantity' => $validated['output_quantity'] ?? 0,
|
||||
'output_batch_number' => $validated['output_batch_number'],
|
||||
'output_batch_number' => $request->output_batch_number ?? $productionOrder->output_batch_number,
|
||||
'output_box_count' => $request->output_box_count,
|
||||
'production_date' => $validated['production_date'] ?? now()->toDateString(),
|
||||
'expiry_date' => $request->expiry_date,
|
||||
'status' => $status,
|
||||
'production_date' => $request->production_date ?? $productionOrder->production_date,
|
||||
'expiry_date' => $request->expiry_date ?? $productionOrder->expiry_date,
|
||||
'remark' => $request->remark,
|
||||
]);
|
||||
|
||||
@@ -398,38 +353,8 @@ class ProductionOrderController extends Controller
|
||||
'quantity_used' => $item['quantity_used'] ?? 0,
|
||||
'unit_id' => $item['unit_id'] ?? null,
|
||||
]);
|
||||
|
||||
if ($status === 'completed') {
|
||||
$this->inventoryService->decreaseInventoryQuantity(
|
||||
$item['inventory_id'],
|
||||
$item['quantity_used'],
|
||||
"生產單 #{$productionOrder->code} 耗料",
|
||||
ProductionOrder::class,
|
||||
$productionOrder->id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($status === 'completed') {
|
||||
$this->inventoryService->createInventoryRecord([
|
||||
'warehouse_id' => $validated['warehouse_id'],
|
||||
'product_id' => $validated['product_id'],
|
||||
'quantity' => $validated['output_quantity'],
|
||||
'batch_number' => $validated['output_batch_number'],
|
||||
'box_number' => $request->output_box_count,
|
||||
'arrival_date' => $validated['production_date'],
|
||||
'expiry_date' => $request->expiry_date,
|
||||
'reason' => "生產單 #{$productionOrder->code} 成品入庫",
|
||||
'reference_type' => ProductionOrder::class,
|
||||
'reference_id' => $productionOrder->id,
|
||||
]);
|
||||
|
||||
activity()
|
||||
->performedOn($productionOrder)
|
||||
->causedBy(auth()->user())
|
||||
->log('completed');
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('production-orders.index')
|
||||
@@ -437,23 +362,102 @@ class ProductionOrderController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* 刪除生產單
|
||||
* 更新生產工單狀態
|
||||
*/
|
||||
public function updateStatus(Request $request, ProductionOrder $productionOrder)
|
||||
{
|
||||
$newStatus = $request->input('status');
|
||||
|
||||
if (!$productionOrder->canTransitionTo($newStatus)) {
|
||||
return response()->json(['error' => '不合法的狀態轉移或權限不足'], 403);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($newStatus, $productionOrder, $request) {
|
||||
$oldStatus = $productionOrder->status;
|
||||
|
||||
// 1. 執行特定狀態的業務邏輯
|
||||
if ($oldStatus === ProductionOrder::STATUS_APPROVED && $newStatus === ProductionOrder::STATUS_IN_PROGRESS) {
|
||||
// 開始製作 -> 扣除原料庫存
|
||||
$items = $productionOrder->items;
|
||||
foreach ($items as $item) {
|
||||
$this->inventoryService->decreaseInventoryQuantity(
|
||||
$item->inventory_id,
|
||||
$item->quantity_used,
|
||||
"生產單 #{$productionOrder->code} 開始製作 (扣料)",
|
||||
ProductionOrder::class,
|
||||
$productionOrder->id
|
||||
);
|
||||
}
|
||||
}
|
||||
elseif ($oldStatus === ProductionOrder::STATUS_IN_PROGRESS && $newStatus === ProductionOrder::STATUS_COMPLETED) {
|
||||
// 完成製作 -> 成品入庫
|
||||
$warehouseId = $request->input('warehouse_id'); // 由前端 Modal 傳來
|
||||
$batchNumber = $request->input('output_batch_number'); // 由前端 Modal 傳來
|
||||
$expiryDate = $request->input('expiry_date'); // 由前端 Modal 傳來
|
||||
|
||||
if (!$warehouseId) {
|
||||
throw new \Exception('必須選擇入庫倉庫');
|
||||
}
|
||||
if (!$batchNumber) {
|
||||
throw new \Exception('必須提供成品批號');
|
||||
}
|
||||
|
||||
// 更新單據資訊:批號、效期與自動記錄生產日期
|
||||
$productionOrder->output_batch_number = $batchNumber;
|
||||
$productionOrder->expiry_date = $expiryDate;
|
||||
$productionOrder->production_date = now()->toDateString();
|
||||
$productionOrder->warehouse_id = $warehouseId;
|
||||
|
||||
$this->inventoryService->createInventoryRecord([
|
||||
'warehouse_id' => $warehouseId,
|
||||
'product_id' => $productionOrder->product_id,
|
||||
'quantity' => $productionOrder->output_quantity,
|
||||
'batch_number' => $batchNumber,
|
||||
'box_number' => $productionOrder->output_box_count,
|
||||
'arrival_date' => now()->toDateString(),
|
||||
'expiry_date' => $expiryDate,
|
||||
'reason' => "生產單 #{$productionOrder->code} 製作完成 (入庫)",
|
||||
'reference_type' => ProductionOrder::class,
|
||||
'reference_id' => $productionOrder->id,
|
||||
]);
|
||||
}
|
||||
|
||||
// 2. 更新狀態
|
||||
$productionOrder->status = $newStatus;
|
||||
$productionOrder->save();
|
||||
|
||||
// 3. 紀錄 Activity Log
|
||||
activity()
|
||||
->performedOn($productionOrder)
|
||||
->causedBy(auth()->user())
|
||||
->withProperties([
|
||||
'old_status' => $oldStatus,
|
||||
'new_status' => $newStatus
|
||||
])
|
||||
->log("status_updated_to_{$newStatus}");
|
||||
});
|
||||
|
||||
return back()->with('success', '狀態已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* 從儲存體中移除指定資源。
|
||||
*/
|
||||
public function destroy(ProductionOrder $productionOrder)
|
||||
{
|
||||
if ($productionOrder->status === 'completed') {
|
||||
return redirect()->back()->with('error', '已完工的生產單無法刪除');
|
||||
// 僅允許刪除草稿或已作廢的單據
|
||||
if (!in_array($productionOrder->status, [ProductionOrder::STATUS_DRAFT, ProductionOrder::STATUS_CANCELLED])) {
|
||||
return redirect()->back()->with('error', '僅有草稿或已作廢的生產單可以刪除');
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($productionOrder) {
|
||||
// 紀錄刪除動作 (需在刪除前或使用軟刪除)
|
||||
$productionOrder->items()->delete();
|
||||
$productionOrder->delete();
|
||||
|
||||
activity()
|
||||
->performedOn($productionOrder)
|
||||
->causedBy(auth()->user())
|
||||
->log('deleted');
|
||||
|
||||
$productionOrder->items()->delete();
|
||||
$productionOrder->delete();
|
||||
});
|
||||
|
||||
return redirect()->route('production-orders.index')->with('success', '生產單已刪除');
|
||||
|
||||
Reference in New Issue
Block a user