connection = $mirror->connection(); } public function ready(): bool { return $this->connection->getSchemaBuilder()->hasTable(self::TABLE); } /** * @param array{company_name?:string,tag?:string} $filters * @return array{data: list>, total: int, summary: array} */ public function paginate(int $page, int $perPage, array $filters = [], ?string $sort = null, string $direction = 'asc'): array { if (! $this->ready()) { return [ 'data' => [], 'total' => 0, 'summary' => [ 'companies' => 0, 'projected_sales' => 0.0, 'receipt_total' => 0.0, 'remaining_gap' => 0.0, 'average_invoicing_ratio' => 0.0, ], ]; } $query = $this->connection->table(self::TABLE); $companyName = trim((string) ($filters['company_name'] ?? '')); if ($companyName !== '') { $query->where('company_name', 'like', '%' . addcslashes($companyName, '%_\\') . '%'); } $tag = trim((string) ($filters['tag'] ?? '')); if ($tag !== '') { $query->where('tag', '=', $tag); } $total = (clone $query)->count(); $totals = (clone $query)->selectRaw( 'COUNT(*) AS companies, ' . 'COALESCE(SUM(projected_sales), 0) AS projected_sales, ' . 'COALESCE(SUM(receipt_total), 0) AS receipt_total, ' . 'COALESCE(SUM(remaining_gap), 0) AS remaining_gap', )->first(); $ratios = (clone $query) ->whereNotNull('invoicing_ratio') ->pluck('invoicing_ratio') ->map(static function (mixed $value): ?float { $text = str_replace([',', ' ', '%'], '', trim((string) $value)); if ($text === '' || $text === '/' || ! is_numeric($text)) { return null; } $ratio = (float) $text; if (str_contains((string) $value, '%') || $ratio > 1) { $ratio /= 100; } return $ratio >= 0 ? $ratio : null; }) ->filter(static fn (?float $value): bool => $value !== null) ->values(); $column = in_array($sort, self::SORTABLE, true) ? $sort : null; $direction = strtolower($direction) === 'desc' ? 'desc' : 'asc'; if ($column !== null) { $query->orderBy($column, $direction)->orderBy('sort_order'); } else { $query->orderBy('sort_order')->orderBy('id'); } $rows = $query ->forPage(max($page, 1), min(max($perPage, 1), 100)) ->get() ->map(static fn (object $row): array => (array) $row) ->all(); return [ 'data' => $rows, 'total' => $total, 'summary' => [ 'companies' => (int) ($totals->companies ?? $total), 'projected_sales' => round((float) ($totals->projected_sales ?? 0), 2), 'receipt_total' => round((float) ($totals->receipt_total ?? 0), 2), 'remaining_gap' => round((float) ($totals->remaining_gap ?? 0), 2), 'average_invoicing_ratio' => round((float) ($ratios->avg() ?? 0) * 100, 2), ], ]; } }