available === null) { try { $this->available = Schema::hasTable(self::TABLE) && DB::table(self::TABLE)->exists(); } catch (\Throwable) { $this->available = false; } } return $this->available; } public function covers(string $date): bool { $period = self::period($date); return $period !== null && $this->available() && DB::table(self::TABLE)->where('source_period', $period)->exists(); } /** * @param array $filters * @return array{rows:list>,has_more:bool,date:string} */ public function paginate(array $filters, int $page, int $limit): array { $date = trim((string) ($filters['date'] ?? '')); $period = self::period($date); $limit = min(max($limit, 1), 100); if ($period === null) { return ['rows' => [], 'has_more' => false, 'date' => $date]; } $records = $this->filtered($filters, $period) ->orderByDesc('promotion_fee') ->orderBy('shop_name') ->orderBy('product_code') ->orderBy('id') ->forPage(max($page, 1), $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, 'date' => $date, ]; } /** @return list */ public function shopNames(string $date): array { $period = self::period($date); if ($period === null || ! $this->available()) { return []; } return Cache::remember( 'order-data.promotion.imported-shop-options:' . $period, now()->addHours(24), static fn (): array => DB::table(self::TABLE) ->where('source_period', $period) ->whereNotNull('shop_name') ->where('shop_name', '<>', '') ->distinct() ->orderBy('shop_name') ->pluck('shop_name') ->map(static fn (mixed $name): string => trim((string) $name)) ->filter() ->values() ->all(), ); } /** * @param array $payload */ public static function toRecord( array $payload, string $period, ?string $importedAt = null, ): array { ksort($payload); $rowKey = sha1((string) json_encode( ['source_period' => $period, 'payload' => $payload], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, )); $now = $importedAt ?? now()->toDateTimeString(); return [ 'row_key' => $rowKey, 'source_period' => $period, 'shop_name' => mb_substr((string) ($payload['shop_name'] ?? ''), 0, 191), 'product_code' => mb_substr((string) ($payload['product_code'] ?? ''), 0, 191), 'style_code' => mb_substr((string) ($payload['style_code'] ?? ''), 0, 191), 'shop_product_code' => mb_substr((string) ($payload['shop_product_code'] ?? ''), 0, 191), 'shop_style_code' => mb_substr((string) ($payload['shop_style_code'] ?? ''), 0, 191), 'online_color_spec' => mb_substr((string) ($payload['online_color_spec'] ?? ''), 0, 500), 'promotion_fee' => (float) ($payload['promotion_fee'] ?? 0), '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, string $period): Builder { $query = DB::table(self::TABLE)->where('source_period', $period); $shops = array_values(array_filter(array_map( static fn (mixed $value): string => trim((string) $value), (array) ($filters['shop_names'] ?? []), ), static fn (string $value): bool => $value !== '')); if ($shops === [] && trim((string) ($filters['shop_name'] ?? '')) !== '') { $shops = [trim((string) $filters['shop_name'])]; } if ($shops !== []) { if (! empty($filters['shop_names_exact'])) { $query->whereIn('shop_name', $shops); } else { $query->where(function (Builder $shop) use ($shops): void { foreach ($shops as $name) { $shop->orWhereRaw("shop_name LIKE ? ESCAPE '!'", [self::contains($name)]); } }); } } $needles = self::batchValues((string) ($filters['sku_id'] ?? '')); if ($needles !== []) { $query->where(function (Builder $sku) use ($needles): void { foreach ($needles as $needle) { $contains = self::contains($needle); $sku->orWhere(function (Builder $group) use ($contains): void { $group->whereRaw("product_code LIKE ? ESCAPE '!'", [$contains]) ->orWhereRaw("style_code LIKE ? ESCAPE '!'", [$contains]) ->orWhereRaw("shop_product_code LIKE ? ESCAPE '!'", [$contains]) ->orWhereRaw("shop_style_code LIKE ? ESCAPE '!'", [$contains]); }); } }); } return $query; } private static function period(string $date): ?string { return preg_match('/^\d{4}-\d{2}/', trim($date), $matches) === 1 ? $matches[0] : null; } /** @return list */ private static function batchValues(string $value): array { $parts = preg_split('/[\s,,;;]+/u', trim($value)) ?: []; $values = []; foreach ($parts as $part) { $part = trim($part); if ($part !== '') { $values[$part] = true; } if (count($values) >= 100) { break; } } return array_keys($values); } private static function contains(string $value): string { return '%' . str_replace(['!', '%', '_'], ['!!', '!%', '!_'], trim($value)) . '%'; } }