Asia/Taipei) // 日期欄位:Laravel 時區已設為 Asia/Taipei,直接使用 actual_time $timeColumn = "inventory_transactions.actual_time"; // 建立查詢 // 我們需要針對每個 品項 在選定區間內 進行彙總 // 來源:inventory_transactions -> inventory -> product $query = InventoryTransaction::query() ->select([ 'products.code as product_code', 'products.name as product_name', 'categories.name as category_name', 'products.id as product_id', // 進貨量:type 為 入庫, 手動入庫 (排除 調撥入庫) DB::raw("SUM(CASE WHEN inventory_transactions.type IN ('入庫', '手動入庫') AND inventory_transactions.quantity > 0 THEN inventory_transactions.quantity ELSE 0 END) as inbound_qty"), // 出貨量:type 為 出庫 (排除 調撥出庫) (取絕對值) DB::raw("ABS(SUM(CASE WHEN inventory_transactions.type IN ('出庫') AND inventory_transactions.quantity < 0 THEN inventory_transactions.quantity ELSE 0 END)) as outbound_qty"), // 調整量:type 為 庫存調整, 手動編輯 DB::raw("SUM(CASE WHEN inventory_transactions.type IN ('庫存調整', '手動編輯') THEN inventory_transactions.quantity ELSE 0 END) as adjust_qty"), // 調撥淨額 (隱藏欄位,但包含在 net_change) // 淨變動:總和 (包含所有類型:進貨、出貨、調整、調撥) DB::raw("SUM(inventory_transactions.quantity) as net_change"), ]) ->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id') ->join('products', 'inventories.product_id', '=', 'products.id') ->leftJoin('categories', 'products.category_id', '=', 'categories.id'); // 日期篩選:資料庫儲存的是台北時間,直接用字串比對 if ($dateFrom && $dateTo) { $query->whereRaw("$timeColumn >= ? AND $timeColumn <= ?", [ $dateFrom . ' 00:00:00', $dateTo . ' 23:59:59' ]); } elseif ($dateFrom) { $query->whereRaw("$timeColumn >= ?", [$dateFrom . ' 00:00:00']); } elseif ($dateTo) { $query->whereRaw("$timeColumn <= ?", [$dateTo . ' 23:59:59']); } // 應用篩選 if ($warehouseId) { $query->where('inventories.warehouse_id', $warehouseId); } if ($categoryId) { $query->where('products.category_id', $categoryId); } if ($search) { $query->where(function ($q) use ($search) { $q->where('products.name', 'like', "%{$search}%") ->orWhere('products.code', 'like', "%{$search}%"); }); } // 分組與排序 $query->groupBy([ 'products.id', 'products.code', 'products.name', 'categories.name' ]); $query->orderBy('products.code', 'asc'); if ($perPage) { return $query->paginate($perPage)->withQueryString(); } return $query->get(); } /** * 取得報表統計數據 (不分頁,針對篩選條件的全量統計) */ public function getSummary(array $filters) { $dateFrom = $filters['date_from'] ?? null; $dateTo = $filters['date_to'] ?? null; $warehouseId = $filters['warehouse_id'] ?? null; $categoryId = $filters['category_id'] ?? null; $search = $filters['search'] ?? null; // 若無任何篩選條件,直接回傳零值 if (!$dateFrom && !$dateTo && !$warehouseId && !$categoryId && !$search) { return (object)[ 'total_inbound' => 0, 'total_outbound' => 0, 'total_adjust' => 0, 'total_net_change' => 0, ]; } // 日期欄位:Laravel 時區已設為 Asia/Taipei,直接使用 actual_time $timeColumn = "inventory_transactions.actual_time"; $query = InventoryTransaction::query() ->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id') ->join('products', 'inventories.product_id', '=', 'products.id') ->leftJoin('categories', 'products.category_id', '=', 'categories.id'); // 日期篩選:資料庫儲存的是台北時間,直接用字串比對 if ($dateFrom && $dateTo) { $query->whereRaw("$timeColumn >= ? AND $timeColumn <= ?", [ $dateFrom . ' 00:00:00', $dateTo . ' 23:59:59' ]); } elseif ($dateFrom) { $query->whereRaw("$timeColumn >= ?", [$dateFrom . ' 00:00:00']); } elseif ($dateTo) { $query->whereRaw("$timeColumn <= ?", [$dateTo . ' 23:59:59']); } if ($warehouseId) { $query->where('inventories.warehouse_id', $warehouseId); } if ($categoryId) { $query->where('products.category_id', $categoryId); } if ($search) { $query->where(function ($q) use ($search) { $q->where('products.name', 'like', "%{$search}%") ->orWhere('products.code', 'like', "%{$search}%"); }); } // 直接聚合所有符合條件的交易 return $query->select([ DB::raw("SUM(CASE WHEN inventory_transactions.type IN ('入庫', '手動入庫') AND inventory_transactions.quantity > 0 THEN inventory_transactions.quantity ELSE 0 END) as total_inbound"), DB::raw("ABS(SUM(CASE WHEN inventory_transactions.type IN ('出庫') AND inventory_transactions.quantity < 0 THEN inventory_transactions.quantity ELSE 0 END)) as total_outbound"), DB::raw("SUM(CASE WHEN inventory_transactions.type IN ('庫存調整', '手動編輯') THEN inventory_transactions.quantity ELSE 0 END) as total_adjust"), DB::raw("SUM(inventory_transactions.quantity) as total_net_change"), ])->first(); } /** * 取得特定商品的庫存異動明細 */ public function getProductDetails($productId, array $filters, ?int $perPage = 20) { $dateFrom = $filters['date_from'] ?? null; $dateTo = $filters['date_to'] ?? null; $warehouseId = $filters['warehouse_id'] ?? null; // 日期欄位:Laravel 時區已設為 Asia/Taipei,直接使用 actual_time $timeColumn = "inventory_transactions.actual_time"; $query = InventoryTransaction::query() ->select([ 'inventory_transactions.*', 'inventories.warehouse_id', 'inventories.batch_number as batch_no', 'warehouses.name as warehouse_name', 'users.name as user_name', 'products.code as product_code', 'products.name as product_name', 'units.name as unit_name' ]) ->join('inventories', 'inventory_transactions.inventory_id', '=', 'inventories.id') ->join('products', 'inventories.product_id', '=', 'products.id') ->leftJoin('units', 'products.base_unit_id', '=', 'units.id') ->leftJoin('warehouses', 'inventories.warehouse_id', '=', 'warehouses.id') ->leftJoin('users', 'inventory_transactions.user_id', '=', 'users.id') ->where('products.id', $productId); // 日期篩選:資料庫儲存的是台北時間,直接用字串比對 if ($dateFrom && $dateTo) { $query->whereRaw("$timeColumn >= ? AND $timeColumn <= ?", [ $dateFrom . ' 00:00:00', $dateTo . ' 23:59:59' ]); } elseif ($dateFrom) { $query->whereRaw("$timeColumn >= ?", [$dateFrom . ' 00:00:00']); } elseif ($dateTo) { $query->whereRaw("$timeColumn <= ?", [$dateTo . ' 23:59:59']); } if ($warehouseId) { $query->where('inventories.warehouse_id', $warehouseId); } // 排序:最新的在最上面 $query->orderBy('inventory_transactions.actual_time', 'desc') ->orderBy('inventory_transactions.id', 'desc'); return $query->paginate($perPage)->withQueryString(); } }