adjustService = $adjustService; } public function index(Request $request) { $query = InventoryAdjustDoc::query() ->with(['createdBy', 'postedBy', 'warehouse']); // 搜尋 if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('doc_no', 'like', "%{$search}%") ->orWhere('reason', 'like', "%{$search}%") ->orWhere('remarks', 'like', "%{$search}%"); }); } if ($request->filled('warehouse_id')) { $query->where('warehouse_id', $request->warehouse_id); } $perPage = $request->input('per_page', 10); $docs = $query->orderByDesc('created_at') ->paginate($perPage) ->withQueryString() ->through(function ($doc) { return [ 'id' => (string) $doc->id, 'doc_no' => $doc->doc_no, 'status' => $doc->status, 'warehouse_name' => $doc->warehouse->name, 'reason' => $doc->reason, 'created_at' => $doc->created_at->format('Y-m-d H:i'), 'posted_at' => $doc->posted_at ? $doc->posted_at->format('Y-m-d H:i') : '-', 'created_by' => $doc->createdBy?->name, 'remarks' => $doc->remarks, ]; }); return Inertia::render('Inventory/Adjust/Index', [ 'docs' => $docs, 'warehouses' => Warehouse::all()->map(fn($w) => ['id' => (string)$w->id, 'name' => $w->name]), 'filters' => $request->only(['warehouse_id', 'search', 'per_page']), ]); } public function store(Request $request) { // 模式 1: 從盤點單建立 if ($request->filled('count_doc_id')) { $countDoc = InventoryCountDoc::findOrFail($request->count_doc_id); if ($countDoc->status !== 'completed') { $errorMsg = $countDoc->status === 'no_adjust' ? '此盤點單無庫存差異,無需建立盤調單' : '只有已完成盤點的單據可以建立盤調單'; return redirect()->back()->with('error', $errorMsg); } // 檢查是否已存在對應的盤調單 (避免重複建立) if (InventoryAdjustDoc::where('count_doc_id', $countDoc->id)->exists()) { return redirect()->back()->with('error', '此盤點單已建立過盤調單'); } $doc = $this->adjustService->createFromCountDoc($countDoc, auth()->id()); return redirect()->route('inventory.adjust.show', [$doc->id]) ->with('success', '已從盤點單生成盤調單'); } // 模式 2: 一般手動調整 (保留原始邏輯但更新訊息) $validated = $request->validate([ 'warehouse_id' => 'required', 'reason' => 'required|string', 'remarks' => 'nullable|string', ]); $doc = $this->adjustService->createDoc( $validated['warehouse_id'], $validated['reason'], $validated['remarks'], auth()->id() ); return redirect()->route('inventory.adjust.show', [$doc->id]) ->with('success', '已建立盤調單'); } /** * API: 獲取可盤調的已完成盤點單 (支援掃描單號) */ public function getPendingCounts(Request $request) { $query = InventoryCountDoc::where('status', 'completed') ->whereNotExists(function ($query) { $query->select(DB::raw(1)) ->from('inventory_adjust_docs') ->whereColumn('inventory_adjust_docs.count_doc_id', 'inventory_count_docs.id'); }); if ($request->filled('search')) { $search = $request->search; $query->where('doc_no', 'like', "%{$search}%"); } $counts = $query->limit(10)->get()->map(function($c) { return [ 'id' => (string)$c->id, 'doc_no' => $c->doc_no, 'warehouse_name' => $c->warehouse->name, 'completed_at' => $c->completed_at->format('Y-m-d H:i'), ]; }); return response()->json($counts); } public function update(Request $request, InventoryAdjustDoc $doc) { $action = $request->input('action', 'update'); if ($action === 'post') { if ($doc->status !== 'draft') { return redirect()->back()->with('error', '只有草稿狀態的單據可以過帳'); } $this->adjustService->post($doc, auth()->id()); return redirect()->back()->with('success', '單據已過帳'); } if ($action === 'void') { if ($doc->status !== 'draft') { return redirect()->back()->with('error', '只有草稿狀態的單據可以作廢'); } $this->adjustService->void($doc, auth()->id()); return redirect()->back()->with('success', '單據已作廢'); } // 一般更新 (更新品項與基本資訊) if ($doc->status !== 'draft') { return redirect()->back()->with('error', '只有草稿狀態的單據可以修改'); } $request->validate([ 'reason' => 'required|string', 'remarks' => 'nullable|string', 'items' => 'required|array|min:1', 'items.*.product_id' => 'required', 'items.*.adjust_qty' => 'required|numeric', ]); $doc->update([ 'reason' => $request->reason, 'remarks' => $request->remarks, ]); $this->adjustService->updateItems($doc, $request->items); return redirect()->back()->with('success', '單據已更新'); } public function show(InventoryAdjustDoc $doc) { $doc->load(['items.product.baseUnit', 'createdBy', 'postedBy', 'warehouse', 'countDoc']); // Pre-fetch relevant Inventory information (mainly for expiry date) $inventoryMap = \App\Modules\Inventory\Models\Inventory::withTrashed() ->where('warehouse_id', $doc->warehouse_id) ->whereIn('product_id', $doc->items->pluck('product_id')) ->whereIn('batch_number', $doc->items->pluck('batch_number')) ->get() ->mapWithKeys(function ($inv) { return [$inv->product_id . '-' . $inv->batch_number => $inv]; }); $docData = [ 'id' => (string) $doc->id, 'doc_no' => $doc->doc_no, 'warehouse_id' => (string) $doc->warehouse_id, 'warehouse_name' => $doc->warehouse->name, 'status' => $doc->status, 'reason' => $doc->reason, 'remarks' => $doc->remarks, 'created_at' => $doc->created_at->format('Y-m-d H:i'), 'created_by' => $doc->createdBy?->name, 'count_doc_id' => $doc->count_doc_id ? (string)$doc->count_doc_id : null, 'count_doc_no' => $doc->countDoc?->doc_no, 'items' => $doc->items->map(function ($item) use ($inventoryMap) { $inv = $inventoryMap->get($item->product_id . '-' . $item->batch_number); 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, 'expiry_date' => $inv && $inv->expiry_date ? $inv->expiry_date->format('Y-m-d') : null, 'unit' => $item->product->baseUnit?->name, 'qty_before' => (float) $item->qty_before, 'adjust_qty' => (float) $item->adjust_qty, 'notes' => $item->notes, ]; }), ]; return Inertia::render('Inventory/Adjust/Show', [ 'doc' => $docData, ]); } public function destroy(InventoryAdjustDoc $doc) { if ($doc->status !== 'draft') { return redirect()->back()->with('error', '只能刪除草稿狀態的單據'); } $doc->items()->delete(); $doc->delete(); return redirect()->route('inventory.adjust.index') ->with('success', '盤調單已刪除'); } }