diff --git a/app/Modules/Inventory/Services/InventoryService.php b/app/Modules/Inventory/Services/InventoryService.php index 451bf60..93bd89a 100644 --- a/app/Modules/Inventory/Services/InventoryService.php +++ b/app/Modules/Inventory/Services/InventoryService.php @@ -246,8 +246,37 @@ class InventoryService implements InventoryServiceInterface $join->on('inventories.warehouse_id', '=', 'ss.warehouse_id') ->on('inventories.product_id', '=', 'ss.product_id'); }) - ->whereNull('inventories.deleted_at') - ->select([ + ->whereNull('inventories.deleted_at'); + + // 決定是否啟用聚合顯示 (Group By Warehouse + Product) + // 當使用者在儀表板點選「狀態篩選」時,為了讓清單筆數與統計數字精確一致 (Dimension Alignment),必須啟用 GROUP BY + $isGrouped = !empty($filters['status']) || ($filters['view_mode'] ?? '') === 'product'; + + if ($isGrouped) { + $query->select([ + DB::raw('MIN(inventories.id) as id'), // 聚合後的代表 ID + 'inventories.warehouse_id', + 'inventories.product_id', + DB::raw('SUM(inventories.quantity) as quantity'), + DB::raw('GROUP_CONCAT(DISTINCT inventories.batch_number SEPARATOR ", ") as batch_number'), + DB::raw('MAX(inventories.expiry_date) as expiry_date'), + DB::raw('GROUP_CONCAT(DISTINCT inventories.location SEPARATOR ", ") as location'), + 'products.code as product_code', + 'products.name as product_name', + 'categories.name as category_name', + 'warehouses.name as warehouse_name', + 'ss.safety_stock', + ])->groupBy( + 'inventories.warehouse_id', + 'inventories.product_id', + 'products.code', + 'products.name', + 'categories.name', + 'warehouses.name', + 'ss.safety_stock' + ); + } else { + $query->select([ 'inventories.id', 'inventories.warehouse_id', 'inventories.product_id', @@ -256,13 +285,13 @@ class InventoryService implements InventoryServiceInterface 'inventories.expiry_date', 'inventories.location', 'inventories.quality_status', - 'inventories.arrival_date', 'products.code as product_code', 'products.name as product_name', 'categories.name as category_name', 'warehouses.name as warehouse_name', 'ss.safety_stock', ]); + } // 篩選:倉庫 if (!empty($filters['warehouse_id'])) { @@ -386,18 +415,24 @@ class InventoryService implements InventoryServiceInterface $paginated = $query->paginate($perPage)->withQueryString(); // 為每筆紀錄附加最後入庫/出庫時間 + 狀態 - $items = collect($paginated->items())->map(function ($item) use ($today, $expiryThreshold) { - $lastIn = \App\Modules\Inventory\Models\InventoryTransaction::where('inventory_id', $item->id) - ->where('type', '入庫') - ->orderByDesc('actual_time') - ->value('actual_time'); + $items = collect($paginated->items())->map(function ($item) use ($today, $expiryThreshold, $isGrouped) { + // 如果是聚合模式,Transaction 查詢也需要調整或略過單一批次查詢 + $lastIn = null; + $lastOut = null; - $lastOut = \App\Modules\Inventory\Models\InventoryTransaction::where('inventory_id', $item->id) - ->where('type', '出庫') - ->orderByDesc('actual_time') - ->value('actual_time'); + if (!$isGrouped) { + $lastIn = \App\Modules\Inventory\Models\InventoryTransaction::where('inventory_id', $item->id) + ->where('type', '入庫') + ->orderByDesc('actual_time') + ->value('actual_time'); - // 計算狀態 (明細表格依然呈現該批次的狀態) + $lastOut = \App\Modules\Inventory\Models\InventoryTransaction::where('inventory_id', $item->id) + ->where('type', '出庫') + ->orderByDesc('actual_time') + ->value('actual_time'); + } + + // 計算狀態 $statuses = []; if ($item->quantity < 0) { $statuses[] = 'negative'; @@ -427,10 +462,11 @@ class InventoryService implements InventoryServiceInterface 'safety_stock' => $item->safety_stock, 'expiry_date' => $item->expiry_date ? \Carbon\Carbon::parse($item->expiry_date)->toDateString() : null, 'location' => $item->location, - 'quality_status' => $item->quality_status, + 'quality_status' => $item->quality_status ?? null, 'last_inbound' => $lastIn ? \Carbon\Carbon::parse($lastIn)->toDateString() : null, 'last_outbound' => $lastOut ? \Carbon\Carbon::parse($lastOut)->toDateString() : null, 'statuses' => $statuses, + 'is_grouped' => $isGrouped, ]; }); diff --git a/resources/js/Pages/Inventory/StockQuery/Index.tsx b/resources/js/Pages/Inventory/StockQuery/Index.tsx index 3d609af..0eeb948 100644 --- a/resources/js/Pages/Inventory/StockQuery/Index.tsx +++ b/resources/js/Pages/Inventory/StockQuery/Index.tsx @@ -40,6 +40,8 @@ interface InventoryItem { last_inbound: string | null; last_outbound: string | null; statuses: string[]; + is_grouped?: boolean; + location: string | null; } interface PaginationLink { @@ -491,18 +493,13 @@ export default function StockQueryIndex({ {item.warehouse_name} - + {item.batch_number || "—"} - + {item.location || "—"} - + {item.quantity}