>,total:int,totals:array} */ public function boardInventoryDetails(array $filters, int $page, int $limit): array { $where = [ 's.stockType = ?', 's.[month] = (SELECT MAX(latest.[month]) FROM [dbo].[pm_boardInventoryDetail_snapshot] latest WHERE latest.stockType = ?)', ]; $bindings = ['素板', '素板']; if (($filters['manufacturer'] ?? '') !== '') { $where[] = 's.supplier LIKE ? ESCAPE \'\\\''; $bindings[] = $this->contains($filters['manufacturer']); } if (($filters['specification'] ?? '') !== '') { $where[] = 's.name LIKE ? ESCAPE \'\\\''; $bindings[] = $this->contains($filters['specification']); } if (($filters['matching_value'] ?? '') !== '') { $where[] = 's.number LIKE ? ESCAPE \'\\\''; $bindings[] = $this->contains($filters['matching_value']); } if (($filters['date_from'] ?? '') !== '') { $where[] = 'CONVERT(date, COALESCE(s.[date], s.[month])) >= ?'; $bindings[] = $filters['date_from']; } if (($filters['date_to'] ?? '') !== '') { $where[] = 'CONVERT(date, COALESCE(s.[date], s.[month])) <= ?'; $bindings[] = $filters['date_to']; } $page = max($page, 1); $limit = min(max($limit, 1), 100); $offset = ($page - 1) * $limit; $whereSql = implode(' AND ', $where); $sql = "WITH source_rows AS ( SELECT ISNULL(s.supplier, '') AS manufacturer, CONVERT(varchar(10), COALESCE(s.[date], s.[month]), 120) AS [date], ISNULL(s.name, '') AS specification, ISNULL(s.number, '') AS matching_value, CAST(ISNULL(s.lastBalance, 0) AS decimal(18,2)) AS opening_balance, CAST(ISNULL(s.averagePrice, 0) AS decimal(18,2)) AS weighted_average_price, CAST(ISNULL(s.inCount, 0) AS decimal(18,2)) AS stock_in, CAST(ISNULL(s.deliveryCount, 0) AS decimal(18,2)) AS lamination_out, CAST(ISNULL(s.balance, 0) AS decimal(18,2)) AS inventory, CAST(ISNULL(s.surplusPrice, 0) AS decimal(18,2)) AS remaining_value, CAST(ISNULL(s.inCount, 0) * ISNULL(s.averagePrice, 0) AS decimal(18,2)) AS stock_in_amount, CAST(ISNULL(s.balance, 0) * ISNULL(s.averagePrice, 0) AS decimal(18,2)) AS closing_remaining_value, CAST(ISNULL(s.lastBalance, 0) * ISNULL(s.averagePrice, 0) AS decimal(18,2)) AS previous_remaining_value, CAST(ISNULL(s.deliveryCount, 0) * ISNULL(s.averagePrice, 0) AS decimal(18,2)) AS stock_out_amount, CAST(SUM(CAST(ISNULL(s.lastBalance, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_opening_balance, CAST(SUM(CAST(ISNULL(s.inCount, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_stock_in, CAST(SUM(CAST(ISNULL(s.deliveryCount, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_lamination_out, CAST(SUM(CAST(ISNULL(s.balance, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_inventory, CAST(SUM(CAST(ISNULL(s.surplusPrice, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_remaining_value, CAST(SUM(CAST(ISNULL(s.inCount, 0) * ISNULL(s.averagePrice, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_stock_in_amount, CAST(SUM(CAST(ISNULL(s.balance, 0) * ISNULL(s.averagePrice, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_closing_remaining_value, CAST(SUM(CAST(ISNULL(s.lastBalance, 0) * ISNULL(s.averagePrice, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_previous_remaining_value, CAST(SUM(CAST(ISNULL(s.deliveryCount, 0) * ISNULL(s.averagePrice, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_stock_out_amount, ROW_NUMBER() OVER ( ORDER BY COALESCE(s.[date], s.[month]) DESC, s.supplier, s.name, s.number, s.id DESC ) AS row_no, COUNT(*) OVER () AS total_count FROM [dbo].[pm_boardInventoryDetail_snapshot] s WHERE {$whereSql} ) SELECT manufacturer, [date], specification, matching_value, opening_balance, weighted_average_price, stock_in, lamination_out, inventory, remaining_value, stock_in_amount, closing_remaining_value, previous_remaining_value, stock_out_amount, summary_opening_balance, summary_stock_in, summary_lamination_out, summary_inventory, summary_remaining_value, summary_stock_in_amount, summary_closing_remaining_value, summary_previous_remaining_value, summary_stock_out_amount, total_count FROM source_rows WHERE row_no BETWEEN ? AND ? ORDER BY row_no"; $rows = $this->query()->select($sql, array_merge($bindings, [$offset + 1, $offset + $limit])); $totals = array_fill_keys(self::SUMMARY_KEYS, 0.0); if ($rows !== []) { foreach (self::SUMMARY_KEYS as $key) { $totals[$key] = (float) ($rows[0]['summary_' . $key] ?? 0); } } $total = (int) ($rows[0]['total_count'] ?? 0); foreach ($rows as &$row) { unset($row['total_count']); foreach (self::SUMMARY_KEYS as $key) { unset($row['summary_' . $key]); } } return ['data' => $rows, 'total' => $total, 'totals' => $totals]; } private function query(): UpstreamReadOnlyQuery { return $this->query ??= $this->queries->forSource(self::SOURCE); } private function contains(string $value): string { return '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], trim($value)) . '%'; } }