[FEAT] 新增生產工單實際產量欄位與 UI 規範

- 新增 database/migrations/tenant 實際產量與耗損原因
- ProductionOrder API 狀態推進與實際產量計算
- 完工入庫新增實際產出數量原生數字輸入框 (step=1)
- Create.tsx 補上前端資料驗證與狀態保護
- 建立並更新 UI 數字輸入框設計規範
This commit is contained in:
2026-03-10 15:32:52 +08:00
parent adf13410ba
commit 6ca0bafd60
8 changed files with 325 additions and 129 deletions

View File

@@ -134,15 +134,15 @@ class ProductionOrderController extends Controller
public function store(Request $request)
{
$status = $request->input('status', 'draft');
$rules = [
'product_id' => 'required',
'status' => 'nullable|in:draft,completed',
'warehouse_id' => $status === 'completed' ? 'required' : 'nullable',
'output_quantity' => $status === 'completed' ? 'required|numeric|min:0.01' : 'nullable|numeric',
'status' => 'nullable|in:draft,pending,completed',
'warehouse_id' => 'required',
'output_quantity' => 'required|numeric|min:0.01',
'items' => 'nullable|array',
'items.*.inventory_id' => $status === 'completed' ? 'required' : 'nullable',
'items.*.quantity_used' => $status === 'completed' ? 'required|numeric|min:0.0001' : 'nullable|numeric',
'items.*.inventory_id' => 'required',
'items.*.quantity_used' => 'required|numeric|min:0.0001',
];
$validated = $request->validate($rules);
@@ -159,7 +159,7 @@ class ProductionOrderController extends Controller
'production_date' => $request->production_date,
'expiry_date' => $request->expiry_date,
'user_id' => auth()->id(),
'status' => ProductionOrder::STATUS_DRAFT, // 一律存為草稿
'status' => $status ?: ProductionOrder::STATUS_DRAFT,
'remark' => $request->remark,
]);
@@ -414,6 +414,19 @@ class ProductionOrderController extends Controller
return response()->json(['error' => '不合法的狀態轉移或權限不足'], 403);
}
// 送審前的資料完整性驗證
if ($productionOrder->status === ProductionOrder::STATUS_DRAFT && $newStatus === ProductionOrder::STATUS_PENDING) {
if (!$productionOrder->output_quantity || $productionOrder->output_quantity <= 0) {
return back()->with('error', '送審工單前,必須先編輯填寫「生產數量」');
}
if (!$productionOrder->warehouse_id) {
return back()->with('error', '送審工單前,必須先編輯選擇「預計入庫倉庫」');
}
if ($productionOrder->items()->count() === 0) {
return back()->with('error', '送審工單前,請至少新增一項原物料明細');
}
}
DB::transaction(function () use ($newStatus, $productionOrder, $request) {
// 使用鎖定重新獲取單據,防止併發狀態修改
$productionOrder = ProductionOrder::where('id', $productionOrder->id)->lockForUpdate()->first();
@@ -444,6 +457,8 @@ class ProductionOrderController extends Controller
$warehouseId = $request->input('warehouse_id'); // 由前端 Modal 傳來
$batchNumber = $request->input('output_batch_number'); // 由前端 Modal 傳來
$expiryDate = $request->input('expiry_date'); // 由前端 Modal 傳來
$actualOutputQuantity = $request->input('actual_output_quantity'); // 實際產出數量
$lossReason = $request->input('loss_reason'); // 耗損原因
if (!$warehouseId) {
throw new \Exception('必須選擇入庫倉庫');
@@ -451,8 +466,14 @@ class ProductionOrderController extends Controller
if (!$batchNumber) {
throw new \Exception('必須提供成品批號');
}
if (!$actualOutputQuantity || $actualOutputQuantity <= 0) {
throw new \Exception('實際產出數量必須大於 0');
}
if ($actualOutputQuantity > $productionOrder->output_quantity) {
throw new \Exception('實際產出數量不可大於預計產量');
}
// --- 新增:計算原物料投入總成本 ---
// --- 計算原物料投入總成本 ---
$totalCost = 0;
$items = $productionOrder->items()->with('inventory')->get();
foreach ($items as $item) {
@@ -461,23 +482,25 @@ class ProductionOrderController extends Controller
}
}
// 計算單位成本 (若產出數量為 0 則設為 0 避免除以零錯誤)
$unitCost = $productionOrder->output_quantity > 0
? $totalCost / $productionOrder->output_quantity
// 單位成本以「實際產出數量」為分母,反映真實生產效率
$unitCost = $actualOutputQuantity > 0
? $totalCost / $actualOutputQuantity
: 0;
// --------------------------------
// 更新單據資訊:批號、效期與自動記錄生產日期
// 更新單據資訊:批號、效期、實際產量與耗損原因
$productionOrder->output_batch_number = $batchNumber;
$productionOrder->expiry_date = $expiryDate;
$productionOrder->production_date = now()->toDateString();
$productionOrder->warehouse_id = $warehouseId;
$productionOrder->actual_output_quantity = $actualOutputQuantity;
$productionOrder->loss_reason = $lossReason;
// 成品入庫數量改用「實際產出數量」
$this->inventoryService->createInventoryRecord([
'warehouse_id' => $warehouseId,
'product_id' => $productionOrder->product_id,
'quantity' => $productionOrder->output_quantity,
'unit_cost' => $unitCost, // 傳入計算後的單位成本
'quantity' => $actualOutputQuantity,
'unit_cost' => $unitCost,
'batch_number' => $batchNumber,
'box_number' => $productionOrder->output_box_count,
'arrival_date' => now()->toDateString(),