|null */ private ?array $fieldColumns = null; public function ready(): bool { try { return Schema::connection($this->connectionName())->hasTable(self::MAIN_TABLE) && Schema::connection($this->connectionName())->hasTable(self::SCHEMA_TABLE) && $this->initialSyncCompleted() && $this->connection()->table(self::MAIN_TABLE)->exists(); } catch (\Throwable) { return false; } } /** 首次全量同步完成前不对外使用,避免页面读到不完整的数据。 */ private function initialSyncCompleted(): bool { $state = $this->connection()->table('business_mirror_table_states') ->where('dataset_key', self::MAIN_DATASET) ->first(['initial_completed_at']); return $state !== null && $state->initial_completed_at !== null; } /** * 页面字段键 => 上游列名(undefinedN)。 * * @return array */ public function fieldColumns(): array { if ($this->fieldColumns !== null) { return $this->fieldColumns; } $headers = (array) config('order_after_sales_cost.headers', []); $map = []; foreach ($this->connection()->table(self::SCHEMA_TABLE)->orderBy('id')->get(['column', 'name']) as $schema) { $label = self::normalizeLabel((string) $schema->name); $field = $headers[$label] ?? null; // 同一科目出现多次(如 800105佣金 / 800105平台佣金)时保留第一次映射。 if ($field !== null && ! isset($map[$field])) { $map[$field] = (string) $schema->column; } } return $this->fieldColumns = $map; } /** @param array $filters */ public function covers(array $filters): bool { return $this->ready() && $this->filtered($filters, $this->fieldColumns())->limit(1)->exists(); } /** * @param array $filters * @return array{rows:list>,has_more:bool} */ public function paginate(array $filters, int $page, int $limit): array { $page = max($page, 1); $limit = min(max($limit, 1), 100); $columns = $this->fieldColumns(); $select = array_values(array_unique(array_merge(['id'], array_values($columns)))); $orderColumn = $columns['order_time'] ?? null; $query = $this->filtered($filters, $columns)->select($select); if ($orderColumn !== null) { $query->orderByDesc($orderColumn); } $records = $query->orderByDesc('id')->forPage($page, $limit + 1)->get(); $numberKeys = array_flip((array) config('order_after_sales_cost.number_keys', [])); $rows = []; foreach ($records as $record) { $row = []; foreach ($columns as $field => $column) { $value = $record->{$column} ?? null; if (isset($numberKeys[$field])) { $value = $value === null ? null : (float) $value; } $row[$field] = is_string($value) ? trim($value) : $value; } $rows[] = $row; } return [ 'rows' => array_slice($rows, 0, $limit), 'has_more' => count($rows) > $limit, ]; } /** * @param array $filters * @param array $columns */ private function filtered(array $filters, array $columns): \Illuminate\Database\Query\Builder { $query = $this->connection()->table(self::MAIN_TABLE); $from = trim((string) ($filters['from'] ?? '')); $to = trim((string) ($filters['to'] ?? '')); $orderNo = trim((string) ($filters['order_no'] ?? '')); $sku = trim((string) ($filters['sku_id'] ?? '')); $shops = array_values(array_filter((array) ($filters['shop_names'] ?? []), static fn ($v) => trim((string) $v) !== '')); if ($from !== '' && isset($columns['order_time'])) { $query->where($columns['order_time'], '>=', $from . ' 00:00:00'); } if ($to !== '' && isset($columns['order_time'])) { $query->where($columns['order_time'], '<=', $to . ' 23:59:59'); } if ($shops !== [] && isset($columns['shop_name'])) { $query->whereIn($columns['shop_name'], $shops); } if ($orderNo !== '') { $query->where(function ($inner) use ($columns, $orderNo): void { $matched = false; foreach (['online_order_no', 'internal_order_no'] as $field) { if (! isset($columns[$field])) { continue; } $inner->orWhere($columns[$field], 'like', $orderNo . '%'); $matched = true; } if (! $matched) { $inner->whereRaw('1 = 0'); } }); } if ($sku !== '' && isset($columns['product_code'])) { $query->where($columns['product_code'], 'like', '%' . addcslashes($sku, '%_\\') . '%'); } return $query; } private function connection(): \Illuminate\Database\ConnectionInterface { return DB::connection($this->connectionName()); } private function connectionName(): string { return (string) config('upstream_mirrors.connection', 'business_mirror'); } private static function normalizeLabel(string $label): string { $label = str_replace(["\u{3000}", "\u{00A0}"], ' ', $label); $label = preg_replace('/\s+/u', ' ', $label) ?? $label; return trim($label); } }