'logpatient', 'patient' => 'logpatient', 'visit' => 'logpatient', 'logorder' => 'logorder', 'order' => 'logorder', 'specimen' => 'logorder', 'result' => 'logorder', 'logmaster' => 'logmaster', 'master' => 'logmaster', 'config' => 'logmaster', 'valueset' => 'logmaster', 'logsystem' => 'logsystem', 'system' => 'logsystem', 'auth' => 'logsystem', 'job' => 'logsystem', ]; private const PRIMARY_KEYS = [ 'logpatient' => 'LogPatientID', 'logorder' => 'LogOrderID', 'logmaster' => 'LogMasterID', 'logsystem' => 'LogSystemID', ]; private const DEFAULT_PAGE = 1; private const DEFAULT_PER_PAGE = 20; private const MAX_PER_PAGE = 100; private static ?BaseConnection $db = null; public function fetchLogs(array $filters): array { $tableKey = $filters['table'] ?? null; if (empty($tableKey)) { throw new InvalidArgumentException('table parameter is required'); } $logTable = $this->resolveLogTable($tableKey); if ($logTable === null) { throw new InvalidArgumentException("Unknown audit table: {$tableKey}"); } $builder = $this->getDb()->table($logTable); $this->applyFilters($builder, $filters); $total = (int) $builder->countAllResults(false); $page = $this->normalizePage($filters['page'] ?? null); $perPage = $this->normalizePerPage($filters['perPage'] ?? $filters['per_page'] ?? null); $offset = ($page - 1) * $perPage; $builder->orderBy('LogDate', 'DESC'); $builder->orderBy($this->getPrimaryKey($logTable), 'DESC'); $rows = $builder ->limit($perPage, $offset) ->get() ->getResultArray(); return [ 'data' => $rows, 'pagination' => [ 'page' => $page, 'perPage' => $perPage, 'total' => $total, ], ]; } private function applyFilters($builder, array $filters): void { if (!empty($filters['rec_id'])) { $builder->where('RecID', (string) $filters['rec_id']); } if (!empty($filters['event_id'])) { $builder->where('EventID', $this->normalizeCode($filters['event_id'])); } if (!empty($filters['activity_id'])) { $builder->where('ActivityID', $this->normalizeCode($filters['activity_id'])); } $this->applyDateRange($builder, $filters['from'] ?? null, $filters['to'] ?? null); if (!empty($filters['search'])) { $search = trim($filters['search']); if ($search !== '') { $builder->groupStart(); $builder->like('UserID', $search); $builder->orLike('Reason', $search); $builder->orLike('FldName', $search); $builder->orLike('FldValuePrev', $search); $builder->orLike('FldValueNew', $search); $builder->orLike('EventID', $search); $builder->orLike('ActivityID', $search); $builder->groupEnd(); } } } private function applyDateRange($builder, ?string $from, ?string $to): void { if ($from !== null && trim($from) !== '') { $builder->where('LogDate >=', $this->normalizeDate($from)); } if ($to !== null && trim($to) !== '') { $builder->where('LogDate <=', $this->normalizeDate($to)); } } private function normalizeDate(string $value): string { try { $dt = new DateTime($value, new DateTimeZone('UTC')); } catch (Throwable $e) { throw new InvalidArgumentException('Invalid date: ' . $value); } return $dt->format('Y-m-d H:i:s'); } private function normalizeCode(string $value): string { return strtoupper(trim($value)); } private function normalizePage($value): int { $page = (int) ($value ?? self::DEFAULT_PAGE); return $page < 1 ? self::DEFAULT_PAGE : $page; } private function normalizePerPage($value): int { $perPage = (int) ($value ?? self::DEFAULT_PER_PAGE); if ($perPage < 1) { return self::DEFAULT_PER_PAGE; } if ($perPage > self::MAX_PER_PAGE) { throw new InvalidArgumentException('perPage cannot be greater than ' . self::MAX_PER_PAGE); } return $perPage; } private function resolveLogTable(?string $key): ?string { if ($key === null) { return null; } $lookup = strtolower(trim($key)); return self::TABLE_MAP[$lookup] ?? null; } private function getPrimaryKey(string $table): string { return self::PRIMARY_KEYS[$table] ?? 'LogID'; } private function getDb(): BaseConnection { return self::$db ??= \Config\Database::connect(); } }