transferService = $transferService; } public function index(Request $request) { $query = InventoryTransferOrder::query() ->with(['fromWarehouse', 'toWarehouse', 'createdBy', 'postedBy']); // 篩選:若有選定倉庫,則顯示該倉庫作為來源或目的地的調撥單 if ($request->filled('warehouse_id')) { $query->where(function ($q) use ($request) { $q->where('from_warehouse_id', $request->warehouse_id) ->orWhere('to_warehouse_id', $request->warehouse_id); }); } $perPage = $request->input('per_page', 10); $orders = $query->orderByDesc('created_at') ->paginate($perPage) ->withQueryString() ->through(function ($order) { return [ 'id' => (string) $order->id, 'doc_no' => $order->doc_no, 'from_warehouse_name' => $order->fromWarehouse->name, 'to_warehouse_name' => $order->toWarehouse->name, 'status' => $order->status, 'created_at' => $order->created_at->format('Y-m-d H:i'), 'posted_at' => $order->posted_at ? $order->posted_at->format('Y-m-d H:i') : '-', 'created_by' => $order->createdBy?->name, ]; }); return Inertia::render('Inventory/Transfer/Index', [ 'orders' => $orders, 'warehouses' => Warehouse::all()->map(fn($w) => ['id' => (string)$w->id, 'name' => $w->name]), 'filters' => $request->only(['warehouse_id', 'per_page']), ]); } public function store(Request $request) { // 兼容前端不同的參數命名 (from/source, to/target) $fromId = $request->input('from_warehouse_id') ?? $request->input('sourceWarehouseId'); $toId = $request->input('to_warehouse_id') ?? $request->input('targetWarehouseId'); $validated = $request->validate([ 'from_warehouse_id' => 'required_without:sourceWarehouseId|exists:warehouses,id', 'to_warehouse_id' => 'required_without:targetWarehouseId|exists:warehouses,id|different:from_warehouse_id', 'remarks' => 'nullable|string', 'notes' => 'nullable|string', 'instant_post' => 'boolean', // 支援單筆商品直接建立 (撥補單模式) 'product_id' => 'nullable|exists:products,id', 'quantity' => 'nullable|numeric|min:0.01', 'batch_number' => 'nullable|string', ]); $remarks = $validated['remarks'] ?? $validated['notes'] ?? null; $order = $this->transferService->createOrder( $fromId, $toId, $remarks, auth()->id() ); if ($request->input('instant_post') === true) { try { $this->transferService->post($order, auth()->id()); return redirect()->back()->with('success', '撥補成功,庫存已更新'); } catch (\Exception $e) { // 如果過帳失敗,雖然單據已建立,但應回報錯誤 return redirect()->back()->withErrors(['items' => $e->getMessage()]); } } return redirect()->route('inventory.transfer.show', [$order->id]) ->with('success', '已建立調撥單'); } public function show(InventoryTransferOrder $order) { $order->load(['items.product.baseUnit', 'fromWarehouse', 'toWarehouse', 'createdBy', 'postedBy']); $orderData = [ 'id' => (string) $order->id, 'doc_no' => $order->doc_no, 'from_warehouse_id' => (string) $order->from_warehouse_id, 'from_warehouse_name' => $order->fromWarehouse->name, 'to_warehouse_id' => (string) $order->to_warehouse_id, 'to_warehouse_name' => $order->toWarehouse->name, 'status' => $order->status, 'remarks' => $order->remarks, 'created_at' => $order->created_at->format('Y-m-d H:i'), 'created_by' => $order->createdBy?->name, 'items' => $order->items->map(function ($item) use ($order) { // 獲取來源倉庫的當前庫存 $stock = Inventory::where('warehouse_id', $order->from_warehouse_id) ->where('product_id', $item->product_id) ->where('batch_number', $item->batch_number) ->first(); return [ 'id' => (string) $item->id, 'product_id' => (string) $item->product_id, 'product_name' => $item->product->name, 'product_code' => $item->product->code, 'batch_number' => $item->batch_number, 'unit' => $item->product->baseUnit?->name, 'quantity' => (float) $item->quantity, 'max_quantity' => $stock ? (float) $stock->quantity : 0.0, 'notes' => $item->notes, ]; }), ]; return Inertia::render('Inventory/Transfer/Show', [ 'order' => $orderData, ]); } public function update(Request $request, InventoryTransferOrder $order) { if ($order->status !== 'draft') { return redirect()->back()->with('error', '只能修改草稿狀態的單據'); } $validated = $request->validate([ 'items' => 'array', 'items.*.product_id' => 'required|exists:products,id', 'items.*.quantity' => 'required|numeric|min:0.01', 'items.*.batch_number' => 'nullable|string', 'items.*.notes' => 'nullable|string', 'remarks' => 'nullable|string', ]); // 1. 先更新資料 if ($request->has('items')) { $this->transferService->updateItems($order, $validated['items']); } $order->fill($request->only(['remarks'])); // [IMPORTANT] 使用 touch() 確保即便只有品項異動,也會因為 updated_at 變更而觸發自動日誌 $order->touch(); // 2. 判斷是否需要過帳 if ($request->input('action') === 'post') { try { $this->transferService->post($order, auth()->id()); return redirect()->route('inventory.transfer.index') ->with('success', '調撥單已過帳完成'); } catch (\Exception $e) { return redirect()->back()->withErrors(['items' => $e->getMessage()]); } } return redirect()->back()->with('success', '儲存成功'); } public function destroy(InventoryTransferOrder $order) { if ($order->status !== 'draft') { return redirect()->back()->with('error', '只能刪除草稿狀態的單據'); } $order->items()->delete(); $order->delete(); return redirect()->route('inventory.transfer.index') ->with('success', '調撥單已刪除'); } /** * 獲取特定倉庫的庫存列表 (API) - 保留給前端選擇商品用 */ public function getWarehouseInventories(Warehouse $warehouse) { $inventories = $warehouse->inventories() ->with(['product.baseUnit', 'product.category']) ->where('quantity', '>', 0) ->get() ->map(function ($inv) { return [ 'product_id' => (string) $inv->product_id, 'product_name' => $inv->product->name, 'product_code' => $inv->product->code, // Added code 'batch_number' => $inv->batch_number, 'quantity' => (float) $inv->quantity, 'unit_cost' => (float) $inv->unit_cost, 'unit_name' => $inv->product->baseUnit?->name ?? '個', 'expiry_date' => $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null, ]; }); return response()->json($inventories); } }