商品利润镜像:单号查询改走索引,修复页面 504 #471

Merged
renshen merged 2 commits from fix/renshen-afs-order-lookup into main 2026-10-23 13:55:16 +08:00

View File

@ -86,7 +86,33 @@ class GoodsProfitAfsRepository
/** @param array<string,mixed> $filters */
public function covers(array $filters): bool
{
return $this->ready() && $this->filtered($filters, $this->fieldColumns())->limit(1)->exists();
if (! $this->ready()) {
return false;
}
$columns = $this->fieldColumns();
$orderNo = trim((string) ($filters['order_no'] ?? ''));
// 单号查询走各自列的索引,避免 undefined2 OR undefined1 触发全表扫描。
if ($orderNo !== '') {
foreach (['online_order_no', 'internal_order_no'] as $field) {
if (! isset($columns[$field])) {
continue;
}
$query = $this->connection()->table(self::MAIN_TABLE)
->where($columns[$field], 'like', $orderNo . '%');
$this->applyDateRange($query, $filters, $columns);
if ($query->limit(1)->exists()) {
return true;
}
}
return false;
}
return $this->filtered($filters, $columns)->limit(1)->exists();
}
/**
@ -100,6 +126,11 @@ class GoodsProfitAfsRepository
$columns = $this->fieldColumns();
$select = array_values(array_unique(array_merge(['id'], array_values($columns))));
$orderColumn = $columns['order_time'] ?? null;
$orderNo = trim((string) ($filters['order_no'] ?? ''));
if ($orderNo !== '') {
return $this->paginateByOrderNumber($orderNo, $filters, $columns, $select, $page, $limit);
}
$query = $this->filtered($filters, $columns)->select($select);
@ -133,6 +164,75 @@ class GoodsProfitAfsRepository
];
}
/**
* 单号查询:分别走 online/internal 两个索引,合并后在 PHP 排序分页。
*
* @param array<string,mixed> $filters
* @param array<string,string> $columns
* @param list<string> $select
* @return array{rows:list<array<string,mixed>>,has_more:bool}
*/
private function paginateByOrderNumber(
string $orderNo,
array $filters,
array $columns,
array $select,
int $page,
int $limit,
): array {
$records = [];
$cap = max($limit * 4, 400);
foreach (['online_order_no', 'internal_order_no'] as $field) {
if (! isset($columns[$field])) {
continue;
}
$query = $this->connection()->table(self::MAIN_TABLE)
->select($select)
->where($columns[$field], 'like', $orderNo . '%');
$this->applyDateRange($query, $filters, $columns);
foreach ($query->limit($cap)->get() as $record) {
$records[(int) $record->id] = $record;
}
}
$timeColumn = $columns['order_time'] ?? null;
usort($records, static function ($left, $right) use ($timeColumn): int {
$leftTime = $timeColumn !== null ? strtotime(str_replace('/', '-', (string) ($left->{$timeColumn} ?? ''))) : false;
$rightTime = $timeColumn !== null ? strtotime(str_replace('/', '-', (string) ($right->{$timeColumn} ?? ''))) : false;
return [$rightTime ?: 0, (int) $right->id] <=> [$leftTime ?: 0, (int) $left->id];
});
$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;
}
$offset = ($page - 1) * $limit;
return [
'rows' => array_slice($rows, $offset, $limit),
'has_more' => count($rows) > $offset + $limit,
];
}
/**
* @param array<string,mixed> $filters
* @param array<string,string> $columns
@ -189,6 +289,30 @@ class GoodsProfitAfsRepository
return $query;
}
/**
* @param array<string,mixed> $filters
* @param array<string,string> $columns
*/
private function applyDateRange(\Illuminate\Database\Query\Builder $query, array $filters, array $columns): void
{
$from = trim((string) ($filters['from'] ?? ''));
$to = trim((string) ($filters['to'] ?? ''));
if (($from === '' && $to === '') || ! isset($columns['order_time'])) {
return;
}
$expression = $this->dateExpression($columns['order_time']);
if ($from !== '') {
$query->whereRaw($expression . ' >= ?', [$from . ' 00:00:00']);
}
if ($to !== '') {
$query->whereRaw($expression . ' <= ?', [$to . ' 23:59:59']);
}
}
private function dateExpression(string $column): string
{
// Dynamic schemas can move the source field. Keep the original parser