diff --git a/app/Modules/Inventory/Controllers/InventoryController.php b/app/Modules/Inventory/Controllers/InventoryController.php index a7d58cc..6c5f157 100644 --- a/app/Modules/Inventory/Controllers/InventoryController.php +++ b/app/Modules/Inventory/Controllers/InventoryController.php @@ -57,9 +57,31 @@ class InventoryController extends Controller ->pluck('safety_stock', 'product_id') ->mapWithKeys(fn($val, $key) => [(string)$key => (float)$val]); - $items = $warehouse->inventories() - ->with(['product.baseUnit', 'lastIncomingTransaction', 'lastOutgoingTransaction']) - ->get(); + $query = $warehouse->inventories() + ->with(['product.baseUnit', 'lastIncomingTransaction', 'lastOutgoingTransaction']); + + // 加入搜尋過濾 + if ($request->filled('search')) { + $search = $request->input('search'); + $query->where(function ($q) use ($search) { + $q->where('batch_number', 'like', "%{$search}%") + ->orWhere(\Illuminate\Support\Facades\DB::raw("CONCAT('BATCH-', inventories.id)"), 'like', "%{$search}%") + ->orWhereHas('product', function ($pq) use ($search) { + $pq->where('name', 'like', "%{$search}%") + ->orWhere('code', 'like', "%{$search}%"); + }); + }); + } + + // 加入類型過濾 + if ($request->filled('type') && $request->input('type') !== 'all') { + $type = $request->input('type'); + $query->whereHas('product.category', function ($cq) use ($type) { + $cq->where('name', $type); + }); + } + + $items = $query->get(); // 判斷是否為販賣機並調整分組 $isVending = $warehouse->type === 'vending'; diff --git a/app/Modules/Inventory/Exports/StockQueryExport.php b/app/Modules/Inventory/Exports/StockQueryExport.php index 9da737d..3c9d7f3 100644 --- a/app/Modules/Inventory/Exports/StockQueryExport.php +++ b/app/Modules/Inventory/Exports/StockQueryExport.php @@ -58,7 +58,9 @@ class StockQueryExport implements FromCollection, WithHeadings, WithMapping, Sho $search = $this->filters['search']; $query->where(function ($q) use ($search) { $q->where('products.code', 'like', "%{$search}%") - ->orWhere('products.name', 'like', "%{$search}%"); + ->orWhere('products.name', 'like', "%{$search}%") + ->orWhere('inventories.batch_number', 'like', "%{$search}%") + ->orWhere(\Illuminate\Support\Facades\DB::raw("CONCAT('BATCH-', inventories.id)"), 'like', "%{$search}%"); }); } if (!empty($this->filters['status'])) { diff --git a/app/Modules/Inventory/Services/InventoryService.php b/app/Modules/Inventory/Services/InventoryService.php index e43378c..316fb8c 100644 --- a/app/Modules/Inventory/Services/InventoryService.php +++ b/app/Modules/Inventory/Services/InventoryService.php @@ -303,12 +303,14 @@ class InventoryService implements InventoryServiceInterface $query->where('products.category_id', $filters['category_id']); } - // 篩選:關鍵字(商品代碼或名稱) + // 篩選:關鍵字(商品代碼或名稱或批號) if (!empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('products.code', 'like', "%{$search}%") - ->orWhere('products.name', 'like', "%{$search}%"); + ->orWhere('products.name', 'like', "%{$search}%") + ->orWhere('inventories.batch_number', 'like', "%{$search}%") + ->orWhere(\Illuminate\Support\Facades\DB::raw("CONCAT('BATCH-', inventories.id)"), 'like', "%{$search}%"); }); } diff --git a/resources/js/Pages/Inventory/StockQuery/Index.tsx b/resources/js/Pages/Inventory/StockQuery/Index.tsx index f0108ab..d7bd311 100644 --- a/resources/js/Pages/Inventory/StockQuery/Index.tsx +++ b/resources/js/Pages/Inventory/StockQuery/Index.tsx @@ -1,5 +1,6 @@ -import { useState } from "react"; +import { useState, useCallback, useEffect } from "react"; import { Head, router } from "@inertiajs/react"; +import { debounce } from "lodash"; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout"; import { Search, @@ -126,9 +127,14 @@ export default function StockQueryIndex({ filters.per_page || inventories.per_page?.toString() || "10" ); - // 執行篩選 - const applyFilters = (newFilters: Record) => { - const merged = { ...filters, ...newFilters, page: undefined }; + // 同步 URL 的 search 參數到 local state + useEffect(() => { + setSearch(filters.search || ""); + }, [filters.search]); + + // 執行篩選核心 + const applyFiltersWithOptions = (newFilters: Record, currentFilters: typeof filters) => { + const merged = { ...currentFilters, ...newFilters, page: undefined }; // 移除空值 const cleaned: Record = {}; Object.entries(merged).forEach(([key, value]) => { @@ -143,8 +149,27 @@ export default function StockQueryIndex({ }); }; - // 搜尋 + const applyFilters = (newFilters: Record) => { + applyFiltersWithOptions(newFilters, filters); + }; + + // Debounced Search Handler + const debouncedSearch = useCallback( + debounce((term: string, currentFilters: typeof filters) => { + applyFiltersWithOptions({ search: term || undefined }, currentFilters); + }, 500), + [] + ); + + // 搜尋值的改變 + const handleSearchChange = (val: string) => { + setSearch(val); + debouncedSearch(val, filters); + }; + + // 點擊搜尋按鈕或按下 Enter 鍵立即搜尋 const handleSearch = () => { + debouncedSearch.cancel(); applyFilters({ search: search || undefined }); }; @@ -372,9 +397,9 @@ export default function StockQueryIndex({ setSearch(e.target.value)} + onChange={(e) => handleSearchChange(e.target.value)} onKeyDown={handleSearchKeyDown} - placeholder="搜尋商品代碼或名稱..." + placeholder="搜尋商品代碼或名稱或批號..." className="h-9" />