CaiWuXiTong/modules/EcommerceFinance/Repositories/PromotionCostRowRepository.php
renshen 0bf1ae67af 导入订单售后与商品推广月度明细 (#578)
新增 2026-06 月度订单售后及商品推广导入能力
2026-10-26 11:09:22 +08:00

210 lines
6.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Repositories;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* 聚水潭商品推广费用月度导入数据。
*/
class PromotionCostRowRepository
{
private const TABLE = 'ops_promotion_cost_rows';
private ?bool $available = null;
public function available(): bool
{
if ($this->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<string,mixed> $filters
* @return array{rows:list<array<string,mixed>>,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<string> */
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<string,mixed> $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<string,mixed> $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<string> */
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)) . '%';
}
}