filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { $q->where('code', 'like', "%{$search}%") ->orWhere('output_batch_number', 'like', "%{$search}%") ->orWhereHas('product', fn($pq) => $pq->where('name', 'like', "%{$search}%")); }); } // 狀態篩選 if ($request->filled('status') && $request->status !== 'all') { $query->where('status', $request->status); } // 排序 $sortField = $request->input('sort_field', 'created_at'); $sortDirection = $request->input('sort_direction', 'desc'); $allowedSorts = ['id', 'code', 'production_date', 'output_quantity', 'created_at']; if (!in_array($sortField, $allowedSorts)) { $sortField = 'created_at'; } $query->orderBy($sortField, $sortDirection); // 分頁 $perPage = $request->input('per_page', 10); $productionOrders = $query->paginate($perPage)->withQueryString(); return Inertia::render('Production/Index', [ 'productionOrders' => $productionOrders, 'filters' => $request->only(['search', 'status', 'per_page', 'sort_field', 'sort_direction']), ]); } /** * 新增生產單表單 */ public function create(): Response { return Inertia::render('Production/Create', [ 'products' => Product::with(['baseUnit'])->get(), 'warehouses' => Warehouse::all(), 'units' => Unit::all(), ]); } /** * 儲存生產單(含自動扣料與成品入庫) */ public function store(Request $request) { $status = $request->input('status', 'draft'); // 預設為草稿 // 共用驗證規則 $baseRules = [ 'product_id' => 'required|exists:products,id', 'output_batch_number' => 'required|string|max:50', 'status' => 'nullable|in:draft,completed', ]; // 完成模式需要完整驗證 $completedRules = [ 'warehouse_id' => 'required|exists:warehouses,id', 'output_quantity' => 'required|numeric|min:0.01', 'output_box_count' => 'nullable|string|max:10', 'production_date' => 'required|date', 'expiry_date' => 'nullable|date|after_or_equal:production_date', 'remark' => 'nullable|string', 'items' => 'required|array|min:1', 'items.*.inventory_id' => 'required|exists:inventories,id', 'items.*.quantity_used' => 'required|numeric|min:0.0001', 'items.*.unit_id' => 'nullable|exists:units,id', ]; // 草稿模式的寬鬆規則 $draftRules = [ 'warehouse_id' => 'nullable|exists:warehouses,id', 'output_quantity' => 'nullable|numeric|min:0', 'output_box_count' => 'nullable|string|max:10', 'production_date' => 'nullable|date', 'expiry_date' => 'nullable|date', 'remark' => 'nullable|string', 'items' => 'nullable|array', 'items.*.inventory_id' => 'nullable|exists:inventories,id', 'items.*.quantity_used' => 'nullable|numeric|min:0', 'items.*.unit_id' => 'nullable|exists:units,id', ]; $rules = $status === 'completed' ? array_merge($baseRules, $completedRules) : array_merge($baseRules, $draftRules); $validated = $request->validate($rules, [ 'product_id.required' => '請選擇成品商品', 'output_batch_number.required' => '請輸入成品批號', 'warehouse_id.required' => '請選擇入庫倉庫', 'output_quantity.required' => '請輸入生產數量', 'production_date.required' => '請選擇生產日期', 'items.required' => '請至少新增一項原物料', 'items.min' => '請至少新增一項原物料', ]); DB::transaction(function () use ($validated, $request, $status) { // 1. 建立生產工單 $productionOrder = ProductionOrder::create([ 'code' => ProductionOrder::generateCode(), '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_box_count' => $validated['output_box_count'] ?? null, 'production_date' => $validated['production_date'] ?? now()->toDateString(), 'expiry_date' => $validated['expiry_date'] ?? null, 'user_id' => auth()->id(), 'status' => $status, 'remark' => $validated['remark'] ?? null, ]); // 2. 建立明細 (草稿與完成模式皆需儲存) if (!empty($validated['items'])) { foreach ($validated['items'] as $item) { if (empty($item['inventory_id'])) continue; // 建立明細 ProductionOrderItem::create([ 'production_order_id' => $productionOrder->id, 'inventory_id' => $item['inventory_id'], 'quantity_used' => $item['quantity_used'] ?? 0, 'unit_id' => $item['unit_id'] ?? null, ]); // 若為完成模式,則扣減原物料庫存 if ($status === 'completed') { $inventory = Inventory::findOrFail($item['inventory_id']); $inventory->decrement('quantity', $item['quantity_used']); } } } // 3. 若為完成模式,執行成品入庫 if ($status === 'completed') { $product = Product::findOrFail($validated['product_id']); Inventory::create([ 'warehouse_id' => $validated['warehouse_id'], 'product_id' => $validated['product_id'], 'quantity' => $validated['output_quantity'], 'batch_number' => $validated['output_batch_number'], 'box_number' => $validated['output_box_count'], 'origin_country' => 'TW', // 生產預設為本地 'arrival_date' => $validated['production_date'], 'expiry_date' => $validated['expiry_date'] ?? null, 'quality_status' => 'normal', ]); } }); $message = $status === 'completed' ? '生產單已建立,原物料已扣減,成品已入庫' : '生產單草稿已儲存'; return redirect()->route('production-orders.index') ->with('success', $message); } /** * 檢視生產單詳情(含追溯資訊) */ public function show(ProductionOrder $productionOrder): Response { $productionOrder->load([ 'product.baseUnit', 'warehouse', 'user', 'items.inventory.product', 'items.inventory.sourcePurchaseOrder.vendor', 'items.unit', ]); return Inertia::render('Production/Show', [ 'productionOrder' => $productionOrder, ]); } /** * 取得倉庫內可用庫存(供 BOM 選擇) */ public function getWarehouseInventories(Warehouse $warehouse) { $inventories = Inventory::with(['product.baseUnit', 'product.largeUnit']) ->where('warehouse_id', $warehouse->id) ->where('quantity', '>', 0) ->where('quality_status', 'normal') ->orderBy('arrival_date', 'asc') // FIFO:舊的排前面 ->get() ->map(function ($inv) { return [ 'id' => $inv->id, 'product_id' => $inv->product_id, 'product_name' => $inv->product->name, 'product_code' => $inv->product->code, 'batch_number' => $inv->batch_number, 'box_number' => $inv->box_number, 'quantity' => $inv->quantity, 'arrival_date' => $inv->arrival_date?->format('Y-m-d'), 'expiry_date' => $inv->expiry_date?->format('Y-m-d'), 'unit_name' => $inv->product->baseUnit?->name, 'base_unit_id' => $inv->product->base_unit_id, 'base_unit_name' => $inv->product->baseUnit?->name, 'large_unit_id' => $inv->product->large_unit_id, 'large_unit_name' => $inv->product->largeUnit?->name, 'conversion_rate' => $inv->product->conversion_rate, ]; }); return response()->json($inventories); } /** * 編輯生產單(僅限草稿狀態) */ public function edit(ProductionOrder $productionOrder): Response { // 只有草稿可以編輯 if ($productionOrder->status !== 'draft') { return redirect()->route('production-orders.show', $productionOrder->id) ->with('error', '只有草稿狀態的生產單可以編輯'); } $productionOrder->load(['product', 'warehouse', 'items.inventory.product', 'items.unit']); return Inertia::render('Production/Edit', [ 'productionOrder' => $productionOrder, 'products' => Product::with(['baseUnit'])->get(), 'warehouses' => Warehouse::all(), 'units' => Unit::all(), ]); } /** * 更新生產單 */ public function update(Request $request, ProductionOrder $productionOrder) { // 只有草稿可以編輯 if ($productionOrder->status !== 'draft') { return redirect()->route('production-orders.show', $productionOrder->id) ->with('error', '只有草稿狀態的生產單可以編輯'); } $status = $request->input('status', 'draft'); // 共用驗證規則 $baseRules = [ 'product_id' => 'required|exists:products,id', 'output_batch_number' => 'required|string|max:50', 'status' => 'nullable|in:draft,completed', ]; // 完成模式需要完整驗證 $completedRules = [ 'warehouse_id' => 'required|exists:warehouses,id', 'output_quantity' => 'required|numeric|min:0.01', 'output_box_count' => 'nullable|string|max:10', 'production_date' => 'required|date', 'expiry_date' => 'nullable|date|after_or_equal:production_date', 'remark' => 'nullable|string', 'items' => 'required|array|min:1', 'items.*.inventory_id' => 'required|exists:inventories,id', 'items.*.quantity_used' => 'required|numeric|min:0.0001', 'items.*.unit_id' => 'nullable|exists:units,id', ]; // 草稿模式的寬鬆規則 $draftRules = [ 'warehouse_id' => 'nullable|exists:warehouses,id', 'output_quantity' => 'nullable|numeric|min:0', 'output_box_count' => 'nullable|string|max:10', 'production_date' => 'nullable|date', 'expiry_date' => 'nullable|date', 'remark' => 'nullable|string', 'items' => 'nullable|array', 'items.*.inventory_id' => 'nullable|exists:inventories,id', 'items.*.quantity_used' => 'nullable|numeric|min:0', 'items.*.unit_id' => 'nullable|exists:units,id', ]; $rules = $status === 'completed' ? array_merge($baseRules, $completedRules) : array_merge($baseRules, $draftRules); $validated = $request->validate($rules, [ 'product_id.required' => '請選擇成品商品', 'output_batch_number.required' => '請輸入成品批號', 'warehouse_id.required' => '請選擇入庫倉庫', 'output_quantity.required' => '請輸入生產數量', 'production_date.required' => '請選擇生產日期', 'items.required' => '請至少新增一項原物料', 'items.min' => '請至少新增一項原物料', ]); DB::transaction(function () use ($validated, $status, $productionOrder) { // 更新生產工單基本資料 $productionOrder->update([ '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_box_count' => $validated['output_box_count'] ?? null, 'production_date' => $validated['production_date'] ?? now()->toDateString(), 'expiry_date' => $validated['expiry_date'] ?? null, 'status' => $status, 'remark' => $validated['remark'] ?? null, ]); // 刪除舊的明細 $productionOrder->items()->delete(); // 重新建立明細 (草稿與完成模式皆需儲存) if (!empty($validated['items'])) { foreach ($validated['items'] as $item) { if (empty($item['inventory_id'])) continue; ProductionOrderItem::create([ 'production_order_id' => $productionOrder->id, 'inventory_id' => $item['inventory_id'], 'quantity_used' => $item['quantity_used'] ?? 0, 'unit_id' => $item['unit_id'] ?? null, ]); // 若為完成模式,則扣減原物料庫存 if ($status === 'completed') { $inventory = Inventory::findOrFail($item['inventory_id']); $inventory->decrement('quantity', $item['quantity_used']); } } } // 若為完成模式,執行成品入庫 if ($status === 'completed') { Inventory::create([ 'warehouse_id' => $validated['warehouse_id'], 'product_id' => $validated['product_id'], 'quantity' => $validated['output_quantity'], 'batch_number' => $validated['output_batch_number'], 'box_number' => $validated['output_box_count'], 'origin_country' => 'TW', 'arrival_date' => $validated['production_date'], 'expiry_date' => $validated['expiry_date'] ?? null, 'quality_status' => 'normal', ]); } }); $message = $status === 'completed' ? '生產單已完成,原物料已扣減,成品已入庫' : '生產單草稿已更新'; return redirect()->route('production-orders.index') ->with('success', $message); } }