available === null) { try { $this->available = Schema::hasTable(self::TABLE) && DB::table(self::TABLE)->exists(); } catch (\Throwable) { $this->available = false; } } return $this->available; } /** * 导入数据是否覆盖本次筛选(有至少一行命中即视为覆盖)。 * * @param array $filters */ public function covers(array $filters): bool { if (! $this->available()) { return false; } return $this->filtered($filters)->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); $records = $this->filtered($filters) ->orderByDesc('order_time') ->orderByDesc('id') ->forPage($page, $limit + 1) ->get(); $rows = []; foreach ($records as $record) { $payload = json_decode((string) $record->payload, true); $rows[] = is_array($payload) ? $payload : []; } return [ 'rows' => array_slice($rows, 0, $limit), 'has_more' => count($rows) > $limit, ]; } /** * 导入一行(命令内部使用,批量写入)。 * * @param array $payload */ public static function toRecord(array $payload, ?string $importedAt = null): array { // 以整行内容做键:同一单同一商品可能出现多行,只有完全相同的一行才合并。 ksort($payload); $rowKey = sha1((string) json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); $now = $importedAt ?? now()->toDateTimeString(); return [ 'row_key' => $rowKey, 'shop_name' => mb_substr((string) ($payload['shop_name'] ?? ''), 0, 191), 'shop_code' => mb_substr((string) ($payload['shop_code'] ?? ''), 0, 64), 'online_order_no' => mb_substr((string) ($payload['online_order_no'] ?? ''), 0, 64), 'internal_order_no' => mb_substr((string) ($payload['internal_order_no'] ?? ''), 0, 64), 'product_code' => mb_substr((string) ($payload['product_code'] ?? ''), 0, 191), 'order_time' => self::dateTime($payload['order_time'] ?? null), 'product_quantity' => (float) ($payload['product_quantity'] ?? 0), 'product_amount' => (float) ($payload['product_amount'] ?? 0), 'product_cost' => (float) ($payload['product_cost'] ?? 0), 'cost_source' => mb_substr((string) ($payload['cost_source'] ?? ''), 0, 32), 'combine_type' => mb_substr((string) ($payload['combine_type'] ?? ''), 0, 32), 'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'imported_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]; } /** @param array $filters */ private function filtered(array $filters): Builder { $query = DB::table(self::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 !== '') { $query->where('order_time', '>=', $from . ' 00:00:00'); } if ($to !== '') { $query->where('order_time', '<=', $to . ' 23:59:59'); } if ($shops !== []) { $query->whereIn('shop_name', $shops); } if ($orderNo !== '') { $query->where(function (Builder $inner) use ($orderNo): void { $inner->where('online_order_no', 'like', $orderNo . '%') ->orWhere('internal_order_no', 'like', $orderNo . '%') ->orWhere('internal_order_no', '=', $orderNo); }); } if ($sku !== '') { $query->where('product_code', 'like', '%' . addcslashes($sku, '%_\\') . '%'); } return $query; } private static function dateTime(mixed $value): ?string { $value = trim((string) $value); if ($value === '') { return null; } $timestamp = strtotime($value); return $timestamp === false ? null : date('Y-m-d H:i:s', $timestamp); } }