countService = $countService; } public function index(Request $request) { $query = InventoryCountDoc::query() ->with(['createdBy', 'completedBy', 'warehouse']); if ($request->filled('warehouse_id')) { $query->where('warehouse_id', $request->warehouse_id); } if ($request->filled('search')) { $search = $request->search; $query->where(function ($q) use ($search) { $q->where('doc_no', 'like', "%{$search}%") ->orWhere('remarks', 'like', "%{$search}%"); }); } $perPage = $request->input('per_page', 10); if (!in_array($perPage, [10, 20, 50, 100])) { $perPage = 10; } $countQuery = function ($query) { $query->whereNotNull('counted_qty'); }; $docs = $query->withCount(['items', 'items as counted_items_count' => $countQuery]) ->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, 'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d H:i') : '-', 'completed_at' => $doc->completed_at ? $doc->completed_at->format('Y-m-d H:i') : '-', 'created_by' => $doc->createdBy?->name, 'remarks' => $doc->remarks, 'total_items' => $doc->items_count, 'counted_items' => $doc->counted_items_count, ]; }); return Inertia::render('Inventory/Count/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) { $validated = $request->validate([ 'warehouse_id' => 'required|exists:warehouses,id', 'remarks' => 'nullable|string|max:255', ]); $doc = $this->countService->createDoc( $validated['warehouse_id'], $validated['remarks'] ?? null, auth()->id() ); // 自動執行快照 $this->countService->snapshot($doc); return redirect()->route('inventory.count.show', [$doc->id]) ->with('success', '已建立盤點單並完成庫存快照'); } public function show(InventoryCountDoc $doc) { $doc->load(['items.product.baseUnit', 'createdBy', 'completedBy', 'warehouse']); $docData = [ 'id' => (string) $doc->id, 'doc_no' => $doc->doc_no, 'warehouse_id' => (string) $doc->warehouse_id, 'warehouse_name' => $doc->warehouse->name, 'status' => $doc->status, 'remarks' => $doc->remarks, 'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d H:i') : null, 'created_by' => $doc->createdBy?->name, 'items' => $doc->items->map(function ($item) { return [ 'id' => (string) $item->id, 'product_name' => $item->product->name, 'product_code' => $item->product->code, 'batch_number' => $item->batch_number, 'unit' => $item->product->baseUnit?->name, 'system_qty' => (float) $item->system_qty, 'counted_qty' => is_null($item->counted_qty) ? '' : (float) $item->counted_qty, 'diff_qty' => (float) $item->diff_qty, 'notes' => $item->notes, ]; }), ]; return Inertia::render('Inventory/Count/Show', [ 'doc' => $docData, ]); } public function print(InventoryCountDoc $doc) { $doc->load(['items.product.baseUnit', 'createdBy', 'completedBy', 'warehouse']); $docData = [ 'id' => (string) $doc->id, 'doc_no' => $doc->doc_no, 'warehouse_name' => $doc->warehouse->name, 'snapshot_date' => $doc->snapshot_date ? $doc->snapshot_date->format('Y-m-d') : date('Y-m-d'), // Use date only 'created_at' => $doc->created_at->format('Y-m-d'), 'print_date' => date('Y-m-d'), 'created_by' => $doc->createdBy?->name, 'items' => $doc->items->map(function ($item) { return [ 'id' => (string) $item->id, 'product_name' => $item->product->name, 'product_code' => $item->product->code, 'specification' => $item->product->specification, 'unit' => $item->product->baseUnit?->name, 'quantity' => (float) ($item->counted_qty ?? $item->system_qty), // Default to system qty if counted is null, or just counted? User wants "Count Sheet" -> maybe blank if not counted? // Actually, if it's "Completed", we show counted. If it's "Pending", we usually show blank or system. // The 'Show' page logic suggests we show counted_qty. 'counted_qty' => $item->counted_qty, 'notes' => $item->notes, ]; }), ]; return Inertia::render('Inventory/Count/Print', [ 'doc' => $docData, ]); } public function update(Request $request, InventoryCountDoc $doc) { if ($doc->status === 'completed') { return redirect()->back()->with('error', '此盤點單已完成,無法修改'); } $validated = $request->validate([ 'items' => 'array', 'items.*.id' => 'required|exists:inventory_count_items,id', 'items.*.counted_qty' => 'nullable|numeric|min:0', 'items.*.notes' => 'nullable|string', ]); if (isset($validated['items'])) { $this->countService->updateCount($doc, $validated['items']); } // 如果是按了 "完成盤點" if ($request->input('action') === 'complete') { $this->countService->complete($doc, auth()->id()); return redirect()->route('inventory.count.index') ->with('success', '盤點單已完成'); } return redirect()->back()->with('success', '暫存成功'); } public function destroy(InventoryCountDoc $doc) { if ($doc->status === 'completed') { return redirect()->back()->with('error', '已完成的盤點單無法刪除'); } // 記錄活動 activity() ->performedOn($doc) ->causedBy(auth()->user()) ->event('deleted') ->withProperties([ 'snapshot' => [ 'doc_no' => $doc->doc_no, 'warehouse_name' => $doc->warehouse?->name, ] ]) ->log('deleted'); $doc->items()->delete(); $doc->delete(); return redirect()->route('inventory.count.index') ->with('success', '盤點單已刪除'); } }