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}