590 lines
23 KiB
PHP
590 lines
23 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Repositories;
|
||
|
||
use Illuminate\Database\ConnectionInterface;
|
||
use Illuminate\Database\Query\Builder;
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Modules\EcommerceFinance\Services\GoodsProfitAfsLookupSchema;
|
||
|
||
/**
|
||
* 聚水潭「订单利润」页面数据,按订单聚合商品利润镜像明细。
|
||
*
|
||
* 商品成本、退款、退平台补贴等来自订单利润明细镜像;包材费、快递费和
|
||
* 账单费用属于聚水潭页面侧的自动匹配结果,源表中没有字段,未确认规则前
|
||
* 保持为空,不能用 0 代替。
|
||
*/
|
||
class OrderProfitRepository
|
||
{
|
||
private const TABLE = GoodsProfitAfsLookupSchema::TABLE;
|
||
|
||
private const NUMERIC_FIELDS = [
|
||
'product_quantity', 'product_amount', 'product_cost',
|
||
'actual_shipped_quantity', 'actual_shipped_amount', 'actual_shipped_cost',
|
||
'merchant_discount', 'freight_income', 'payment_amount', 'platform_discount',
|
||
'pre_application_quantity', 'pre_application_amount', 'pre_application_cost',
|
||
'pre_return_quantity', 'pre_return_amount', 'pre_return_cost',
|
||
'pre_refund_amount', 'pre_refund_cost', 'pre_platform_subsidy',
|
||
'post_application_quantity', 'post_application_amount', 'post_application_cost',
|
||
'post_return_quantity', 'post_return_amount', 'post_return_cost',
|
||
'post_refund_amount', 'post_refund_cost', 'post_platform_subsidy',
|
||
'defective_quantity',
|
||
];
|
||
|
||
private const SUMMARY_NUMERIC_FIELDS = [
|
||
'cost_amount' => ['product_cost'],
|
||
'product_quantity' => ['product_quantity'],
|
||
'product_amount' => ['product_amount'],
|
||
'actual_shipped_quantity' => ['actual_shipped_quantity'],
|
||
'actual_shipped_amount' => ['actual_shipped_amount'],
|
||
'actual_shipped_cost' => ['actual_shipped_cost'],
|
||
'merchant_discount' => ['merchant_discount'],
|
||
'freight_income' => ['freight_income'],
|
||
'payment_amount' => ['payment_amount'],
|
||
'platform_discount' => ['platform_discount'],
|
||
'refund_amount' => ['pre_refund_amount', 'post_refund_amount'],
|
||
'platform_subsidy' => ['pre_platform_subsidy', 'post_platform_subsidy'],
|
||
'pre_platform_subsidy' => ['pre_platform_subsidy'],
|
||
'pre_application_quantity' => ['pre_application_quantity'],
|
||
'pre_application_amount' => ['pre_application_amount'],
|
||
'pre_application_cost' => ['pre_application_cost'],
|
||
'pre_return_quantity' => ['pre_return_quantity'],
|
||
'pre_return_amount' => ['pre_return_amount'],
|
||
'pre_return_cost' => ['pre_return_cost'],
|
||
'pre_refund_amount' => ['pre_refund_amount'],
|
||
'pre_refund_cost' => ['pre_refund_cost'],
|
||
'post_platform_subsidy' => ['post_platform_subsidy'],
|
||
'post_application_quantity' => ['post_application_quantity'],
|
||
'post_application_amount' => ['post_application_amount'],
|
||
'post_application_cost' => ['post_application_cost'],
|
||
'post_return_quantity' => ['post_return_quantity'],
|
||
'post_return_amount' => ['post_return_amount'],
|
||
'post_return_cost' => ['post_return_cost'],
|
||
'post_refund_amount' => ['post_refund_amount'],
|
||
'post_refund_cost' => ['post_refund_cost'],
|
||
'defective_quantity' => ['defective_quantity'],
|
||
];
|
||
|
||
private const ITEM_FIELDS = [
|
||
'internal_order_no', 'online_order_no', 'sub_order_no', 'after_sales_no',
|
||
'pay_time', 'send_time', 'order_time', 'shop_name', 'product_code', 'style_code',
|
||
'shop_product_code', 'shop_style_code', 'product_name', 'color_spec',
|
||
'product_quantity', 'product_amount', 'product_cost',
|
||
'actual_shipped_quantity', 'actual_shipped_amount', 'actual_shipped_cost',
|
||
'merchant_discount', 'freight_income', 'payment_amount', 'platform_discount',
|
||
'refund_type', 'pre_application_quantity', 'pre_application_amount', 'pre_application_cost',
|
||
'pre_return_quantity', 'pre_return_amount', 'pre_return_cost',
|
||
'pre_refund_amount', 'pre_refund_cost', 'pre_platform_subsidy',
|
||
'post_application_quantity', 'post_application_amount', 'post_application_cost',
|
||
'post_return_quantity', 'post_return_amount', 'post_return_cost',
|
||
'post_refund_amount', 'post_refund_cost', 'post_platform_subsidy', 'defective_quantity',
|
||
'classification_order', 'combine_type', 'shipping_warehouse', 'express_no',
|
||
'express_company', 'order_tags', 'after_sales_tags', 'item_tags',
|
||
];
|
||
|
||
private ?bool $readyCache = null;
|
||
|
||
public function __construct(private readonly ?GoodsProfitAfsRepository $fields = null)
|
||
{
|
||
}
|
||
|
||
public function ready(): bool
|
||
{
|
||
return $this->readyCache ??= ($this->fields ?? app(GoodsProfitAfsRepository::class))->ready();
|
||
}
|
||
|
||
public function status(): array
|
||
{
|
||
try {
|
||
$state = $this->connection()->table('business_mirror_table_states')
|
||
->where('dataset_key', GoodsProfitAfsRepository::MAIN_DATASET)
|
||
->first(['initial_completed_at', 'last_succeeded_at', 'last_error_code']);
|
||
} catch (\Throwable) {
|
||
$state = null;
|
||
}
|
||
|
||
return [
|
||
'ready' => $this->ready(),
|
||
'initial_completed_at' => $state->initial_completed_at ?? null,
|
||
'last_succeeded_at' => $state->last_succeeded_at ?? null,
|
||
'last_error_code' => $state->last_error_code ?? null,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @param array<string,mixed> $filters
|
||
* @return array{rows:list<array<string,mixed>>,has_more:bool}
|
||
*/
|
||
public function paginate(array $filters, int $page, int $limit): array
|
||
{
|
||
if (! $this->hasActiveFilters($filters)) {
|
||
return ['rows' => [], 'has_more' => false];
|
||
}
|
||
|
||
$columns = $this->columns();
|
||
foreach ([
|
||
'shop_name', 'online_order_no', 'internal_order_no', 'order_time',
|
||
'product_cost', 'refund_type', 'post_refund_amount', 'post_platform_subsidy',
|
||
'pre_refund_amount', 'pre_platform_subsidy', 'classification_order',
|
||
] as $field) {
|
||
if (! isset($columns[$field])) {
|
||
return ['rows' => [], 'has_more' => false];
|
||
}
|
||
}
|
||
$dateField = $this->dateField($filters);
|
||
if ($this->hasDateRange($filters)
|
||
&& $this->queryDateColumn($columns, $dateField) === null) {
|
||
return ['rows' => [], 'has_more' => false];
|
||
}
|
||
|
||
$page = max($page, 1);
|
||
$limit = min(max($limit, 1), 100);
|
||
$orderValues = self::batchValues((string) ($filters['order_no'] ?? ''));
|
||
$hasMore = null;
|
||
$selectedGroups = null;
|
||
if ($orderValues !== []) {
|
||
$ids = $this->orderNumberIds($orderValues, $columns);
|
||
if ($ids === []) {
|
||
return ['rows' => [], 'has_more' => false];
|
||
}
|
||
$query = $this->connection()->table(self::TABLE)->whereIn('_mirror_key', $ids);
|
||
$query = $this->filtered(
|
||
$query,
|
||
array_replace($filters, ['order_no' => '', 'from' => '', 'to' => '']),
|
||
$columns,
|
||
);
|
||
} else {
|
||
$filtered = $this->filtered($this->connection()->table(self::TABLE), $filters, $columns);
|
||
$groups = $this->pageOrderGroups($filtered, $columns, $dateField, $page, $limit);
|
||
if ($groups === []) {
|
||
return ['rows' => [], 'has_more' => false];
|
||
}
|
||
|
||
$hasMore = count($groups) > $limit;
|
||
$groups = array_slice($groups, 0, $limit);
|
||
$query = $this->filtered($this->connection()->table(self::TABLE), $filters, $columns);
|
||
$this->constrainToOrderGroups($query, $groups, $columns);
|
||
$selectedGroups = [];
|
||
foreach ($groups as $group) {
|
||
$selectedGroups[$group['shop_name'] . "\0" . $group['order_key']] = true;
|
||
}
|
||
}
|
||
$shop = $this->quote($columns['shop_name']);
|
||
$online = $this->quote($columns['online_order_no']);
|
||
$internal = $this->quote($columns['internal_order_no']);
|
||
$orderTime = $this->quote($columns['order_time']);
|
||
$refundType = $this->quote($columns['refund_type']);
|
||
$categoryOrder = $this->quote($columns['classification_order']);
|
||
$orderKey = "COALESCE(NULLIF(TRIM({$online}), ''), TRIM({$internal}))";
|
||
$sortDate = $this->quote(
|
||
$this->queryDateColumn($columns, $dateField) ?? $columns['order_time'],
|
||
);
|
||
|
||
$query
|
||
->selectRaw("{$shop} AS shop_name")
|
||
->selectRaw("{$online} AS online_order_no")
|
||
->selectRaw("MAX({$internal}) AS internal_order_no")
|
||
->selectRaw("{$orderKey} AS order_key")
|
||
->selectRaw("MAX({$orderTime}) AS order_time")
|
||
->selectRaw("MAX(NULLIF(TRIM({$refundType}), '')) AS refund_type")
|
||
->selectRaw("MAX(NULLIF(TRIM({$categoryOrder}), '')) AS classification_order")
|
||
->selectRaw('COUNT(*) AS source_row_count');
|
||
foreach (self::SUMMARY_NUMERIC_FIELDS as $alias => $fields) {
|
||
$query->selectRaw($this->sumExpression($columns, $fields) . ' AS ' . $alias);
|
||
}
|
||
$records = $query
|
||
->groupByRaw("{$shop}, {$orderKey}")
|
||
->orderByRaw("MAX({$sortDate}) DESC")
|
||
->orderByRaw("MAX({$internal}) DESC");
|
||
if ($selectedGroups === null) {
|
||
$records = $records->forPage($page, $limit + 1)->get();
|
||
} else {
|
||
$records = $records->get()->filter(static function (object $record) use ($selectedGroups): bool {
|
||
return isset($selectedGroups[
|
||
trim((string) ($record->shop_name ?? '')) . "\0"
|
||
. trim((string) ($record->order_key ?? ''))
|
||
]);
|
||
})->values();
|
||
}
|
||
|
||
$rows = $records->map(static function (object $record): array {
|
||
$row = [
|
||
'shop_name' => trim((string) ($record->shop_name ?? '')),
|
||
'online_order_no' => trim((string) ($record->online_order_no ?? '')),
|
||
'internal_order_no' => trim((string) ($record->internal_order_no ?? '')),
|
||
'order_key' => trim((string) ($record->order_key ?? '')),
|
||
'order_time' => $record->order_time,
|
||
'refund_type' => $record->refund_type,
|
||
'classification_order' => $record->classification_order,
|
||
'source_row_count' => (int) ($record->source_row_count ?? 0),
|
||
'matched_package_fee' => null,
|
||
'matched_freight_fee' => null,
|
||
'matched_bill_fee' => null,
|
||
'items' => [],
|
||
];
|
||
foreach (array_keys(self::SUMMARY_NUMERIC_FIELDS) as $field) {
|
||
$row[$field] = round((float) ($record->{$field} ?? 0), 2);
|
||
}
|
||
|
||
return $row;
|
||
})->all();
|
||
|
||
if ($hasMore === null) {
|
||
$hasMore = count($rows) > $limit;
|
||
}
|
||
$rows = array_slice($rows, 0, $limit);
|
||
$this->hydrateItems($rows, $columns);
|
||
|
||
return ['rows' => $rows, 'has_more' => $hasMore];
|
||
}
|
||
|
||
/** @return list<string> */
|
||
public function shopNames(): array
|
||
{
|
||
$cacheKey = 'order-profit.shop-options:undefined8';
|
||
$cached = Cache::get($cacheKey);
|
||
if (is_array($cached)) {
|
||
return $cached;
|
||
}
|
||
if (! $this->ready()) {
|
||
return [];
|
||
}
|
||
|
||
$columns = $this->columns();
|
||
$shopColumn = $columns['shop_name'] ?? null;
|
||
if ($shopColumn === null) {
|
||
return [];
|
||
}
|
||
|
||
$names = $this->connection()->table(self::TABLE)
|
||
->select($shopColumn)
|
||
->whereNotNull($shopColumn)
|
||
->where($shopColumn, '<>', '')
|
||
->distinct()
|
||
->orderBy($shopColumn)
|
||
->pluck($shopColumn)
|
||
->map(static fn (mixed $name): string => trim((string) $name))
|
||
->filter()
|
||
->values()
|
||
->all();
|
||
Cache::put($cacheKey, $names, now()->addHours(24));
|
||
|
||
return $names;
|
||
}
|
||
|
||
/** @return array<string,string> */
|
||
private function columns(): array
|
||
{
|
||
return ($this->fields ?? app(GoodsProfitAfsRepository::class))->fieldColumns();
|
||
}
|
||
|
||
/**
|
||
* @param list<string> $orderValues
|
||
* @param array<string,string> $columns
|
||
* @return list<string>
|
||
*/
|
||
private function orderNumberIds(array $orderValues, array $columns): array
|
||
{
|
||
$ids = [];
|
||
$single = count($orderValues) === 1 ? $orderValues[0] : null;
|
||
foreach (['online_order_no', 'internal_order_no'] as $field) {
|
||
if (! isset($columns[$field])) {
|
||
continue;
|
||
}
|
||
if ($field === 'internal_order_no') {
|
||
$orderValues = array_values(array_filter(
|
||
$orderValues,
|
||
static fn (string $value): bool => mb_strlen($value) <= 11,
|
||
));
|
||
if ($orderValues === []) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
$query = $this->connection()->table(self::TABLE)->select('_mirror_key')->limit(10000);
|
||
if ($single !== null) {
|
||
$query->where($columns[$field], 'like', $single . '%');
|
||
} else {
|
||
$query->whereIn($columns[$field], $orderValues);
|
||
}
|
||
foreach ($query->pluck('_mirror_key') as $id) {
|
||
$ids[(string) $id] = true;
|
||
}
|
||
}
|
||
|
||
return array_keys($ids);
|
||
}
|
||
|
||
/**
|
||
* 先按日期/筛选条件只取当前页的订单键,再对少量订单做宽表汇总。
|
||
* 这样近 7 天未指定店铺时不会对整段宽表逐行汇总后再丢弃分页外数据。
|
||
*
|
||
* @param array<string,string> $columns
|
||
* @return list<array{shop_name:string,online_order_no:string,internal_order_no:string,order_key:string}>
|
||
*/
|
||
private function pageOrderGroups(
|
||
Builder $query,
|
||
array $columns,
|
||
string $dateField,
|
||
int $page,
|
||
int $pageSize,
|
||
): array {
|
||
$shop = $this->quote($columns['shop_name']);
|
||
$online = $this->quote($columns['online_order_no']);
|
||
$internal = $this->quote($columns['internal_order_no']);
|
||
$orderKey = "COALESCE(NULLIF(TRIM({$online}), ''), TRIM({$internal}))";
|
||
$sortDate = $this->quote(
|
||
$this->queryDateColumn($columns, $dateField) ?? $columns['order_time'],
|
||
);
|
||
|
||
return $query
|
||
->selectRaw("{$shop} AS shop_name")
|
||
->selectRaw("{$online} AS online_order_no")
|
||
->selectRaw("MAX({$internal}) AS internal_order_no")
|
||
->selectRaw("{$orderKey} AS order_key")
|
||
->groupByRaw("{$shop}, {$orderKey}")
|
||
->orderByRaw("MAX({$sortDate}) DESC")
|
||
->orderByRaw("MAX({$internal}) DESC")
|
||
->offset(($page - 1) * $pageSize)
|
||
->limit($pageSize + 1)
|
||
->get()
|
||
->map(static fn (object $row): array => [
|
||
'shop_name' => trim((string) ($row->shop_name ?? '')),
|
||
'online_order_no' => trim((string) ($row->online_order_no ?? '')),
|
||
'internal_order_no' => trim((string) ($row->internal_order_no ?? '')),
|
||
'order_key' => trim((string) ($row->order_key ?? '')),
|
||
])
|
||
->all();
|
||
}
|
||
|
||
/**
|
||
* @param list<array{shop_name:string,online_order_no:string,internal_order_no:string,order_key:string}> $groups
|
||
* @param array<string,string> $columns
|
||
*/
|
||
private function constrainToOrderGroups(Builder $query, array $groups, array $columns): void
|
||
{
|
||
$onlineOrders = [];
|
||
$internalOrders = [];
|
||
$shops = [];
|
||
foreach ($groups as $group) {
|
||
$shops[$group['shop_name']] = true;
|
||
if ($group['online_order_no'] !== '') {
|
||
$onlineOrders[$group['online_order_no']] = true;
|
||
} elseif ($group['internal_order_no'] !== '') {
|
||
$internalOrders[$group['internal_order_no']] = true;
|
||
}
|
||
}
|
||
|
||
$query->where(function (Builder $orders) use ($columns, $onlineOrders, $internalOrders): void {
|
||
if ($onlineOrders !== []) {
|
||
$orders->whereIn($columns['online_order_no'], array_keys($onlineOrders));
|
||
}
|
||
if ($internalOrders !== []) {
|
||
$method = $onlineOrders === [] ? 'whereIn' : 'orWhereIn';
|
||
$orders->{$method}($columns['internal_order_no'], array_keys($internalOrders));
|
||
}
|
||
});
|
||
$query->whereIn($columns['shop_name'], array_keys($shops));
|
||
}
|
||
|
||
/**
|
||
* @param array<string,mixed> $filters
|
||
* @param array<string,string> $columns
|
||
*/
|
||
private function filtered(Builder $query, array $filters, array $columns): Builder
|
||
{
|
||
$dateColumn = $this->queryDateColumn($columns, $this->dateField($filters));
|
||
$from = trim((string) ($filters['from'] ?? ''));
|
||
$to = trim((string) ($filters['to'] ?? ''));
|
||
|
||
if ($dateColumn !== null && $from !== '') {
|
||
$query->where($dateColumn, '>=', $from . ' 00:00:00');
|
||
}
|
||
if ($dateColumn !== null && $to !== '') {
|
||
$query->where($dateColumn, '<=', $to . ' 23:59:59');
|
||
}
|
||
|
||
$shops = self::batchValues((string) ($filters['shop_name'] ?? ''));
|
||
if ($shops !== [] && isset($columns['shop_name'])) {
|
||
$query->whereIn($columns['shop_name'], $shops);
|
||
}
|
||
|
||
$orderValues = self::batchValues((string) ($filters['order_no'] ?? ''));
|
||
if ($orderValues !== []) {
|
||
$query->where(function (Builder $order) use ($orderValues, $columns): void {
|
||
foreach (['online_order_no', 'internal_order_no'] as $field) {
|
||
if (! isset($columns[$field])) {
|
||
continue;
|
||
}
|
||
$order->orWhereIn($columns[$field], $orderValues);
|
||
}
|
||
});
|
||
}
|
||
|
||
$skuValues = self::batchValues((string) ($filters['sku_id'] ?? ''));
|
||
if ($skuValues !== [] && isset($columns['product_code'])) {
|
||
$query->whereIn($columns['product_code'], $skuValues);
|
||
}
|
||
|
||
return $query;
|
||
}
|
||
|
||
/**
|
||
* @param list<array<string,mixed>> $rows
|
||
* @param array<string,string> $columns
|
||
*/
|
||
private function hydrateItems(array &$rows, array $columns): void
|
||
{
|
||
if ($rows === []) {
|
||
return;
|
||
}
|
||
|
||
$itemFields = array_values(array_filter(
|
||
self::ITEM_FIELDS,
|
||
static fn (string $field): bool => isset($columns[$field]),
|
||
));
|
||
if ($itemFields === []) {
|
||
return;
|
||
}
|
||
|
||
$onlineValues = array_values(array_filter(array_column($rows, 'online_order_no')));
|
||
$internalValues = array_values(array_filter(array_column($rows, 'internal_order_no')));
|
||
if ($onlineValues === [] && $internalValues === []) {
|
||
return;
|
||
}
|
||
|
||
$query = $this->connection()->table(self::TABLE);
|
||
$query->where(function (Builder $orders) use ($columns, $onlineValues, $internalValues): void {
|
||
if ($onlineValues !== [] && isset($columns['online_order_no'])) {
|
||
$orders->whereIn($columns['online_order_no'], $onlineValues);
|
||
}
|
||
if ($internalValues !== [] && isset($columns['internal_order_no'])) {
|
||
$method = $onlineValues === [] ? 'whereIn' : 'orWhereIn';
|
||
$orders->{$method}($columns['internal_order_no'], $internalValues);
|
||
}
|
||
});
|
||
|
||
$select = [];
|
||
foreach ($itemFields as $field) {
|
||
$select[] = $columns[$field] . ' AS ' . $field;
|
||
}
|
||
$itemsByOrder = [];
|
||
foreach ($query->get($select) as $record) {
|
||
$item = [];
|
||
foreach ($itemFields as $field) {
|
||
$value = $record->{$field} ?? null;
|
||
$item[$field] = in_array($field, self::NUMERIC_FIELDS, true)
|
||
? ($value === null || $value === '' ? null : (float) $value)
|
||
: $value;
|
||
}
|
||
$online = trim((string) ($item['online_order_no'] ?? ''));
|
||
$internal = trim((string) ($item['internal_order_no'] ?? ''));
|
||
$orderKey = $online !== '' ? $online : $internal;
|
||
$itemsByOrder[$orderKey][] = $item;
|
||
}
|
||
|
||
foreach ($rows as &$row) {
|
||
$key = trim((string) ($row['order_key'] ?? ''));
|
||
$row['items'] = $itemsByOrder[$key] ?? [];
|
||
}
|
||
unset($row);
|
||
}
|
||
|
||
/** @param list<string> $fields */
|
||
private function sumExpression(array $columns, array $fields): string
|
||
{
|
||
$parts = [];
|
||
foreach ($fields as $field) {
|
||
if (isset($columns[$field])) {
|
||
$parts[] = 'COALESCE(CAST(NULLIF(TRIM(' . $this->quote($columns[$field])
|
||
. "), '') AS DECIMAL(18,2)), 0)";
|
||
}
|
||
}
|
||
|
||
return $parts === [] ? '0' : 'SUM(' . implode(' + ', $parts) . ')';
|
||
}
|
||
|
||
private function queryDateColumn(array $columns, string $dateField): ?string
|
||
{
|
||
$definition = GoodsProfitAfsLookupSchema::DATE_COLUMNS[$dateField] ?? null;
|
||
if ($definition === null) {
|
||
return null;
|
||
}
|
||
|
||
if (($columns[$dateField] ?? null) === $definition['source']
|
||
&& Schema::connection($this->connectionName())->hasColumn(self::TABLE, $definition['lookup'])) {
|
||
return $definition['lookup'];
|
||
}
|
||
|
||
return $columns[$dateField] ?? null;
|
||
}
|
||
|
||
private function connection(): ConnectionInterface
|
||
{
|
||
return DB::connection($this->connectionName());
|
||
}
|
||
|
||
private function connectionName(): string
|
||
{
|
||
return (string) config('upstream_mirrors.connection', 'business_mirror');
|
||
}
|
||
|
||
private function quote(string $column): string
|
||
{
|
||
if (preg_match('/^[A-Za-z0-9_]+$/', $column) !== 1) {
|
||
throw new \InvalidArgumentException('订单利润字段名不安全。');
|
||
}
|
||
|
||
return '`' . $column . '`';
|
||
}
|
||
|
||
/** @param array<string,mixed> $filters */
|
||
private function hasActiveFilters(array $filters): bool
|
||
{
|
||
foreach (['from', 'to', 'order_no', 'shop_name', 'sku_id'] as $key) {
|
||
if (trim((string) ($filters[$key] ?? '')) !== '') {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/** @param array<string,mixed> $filters */
|
||
private function dateField(array $filters): string
|
||
{
|
||
$dateField = trim((string) ($filters['date_field'] ?? 'pay_time'));
|
||
|
||
return isset(GoodsProfitAfsLookupSchema::DATE_COLUMNS[$dateField])
|
||
? $dateField
|
||
: 'pay_time';
|
||
}
|
||
|
||
/** @param array<string,mixed> $filters */
|
||
private function hasDateRange(array $filters): bool
|
||
{
|
||
return trim((string) ($filters['from'] ?? '')) !== ''
|
||
|| trim((string) ($filters['to'] ?? '')) !== '';
|
||
}
|
||
|
||
/** @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);
|
||
}
|
||
}
|