修复跨月差额筛选重复计算导致页面缓慢 #508

Merged
yueyang merged 2 commits from fix/yueyang-performance-cross-month-fast-path into main 2026-10-24 17:49:30 +08:00
2 changed files with 81 additions and 18 deletions

View File

@ -160,6 +160,8 @@ class Pages extends Controller
'订单实付按应付/已付核对',
];
private const RECEIPT_CROSS_MONTH_REMARK = '差额=跨月收款(合并已对平)';
/** 业绩明细「异常标注」筛选项,取值与同步命令写入的标注一致。 */
private const RECEIPT_REMARK_OPTIONS = [
'账单无单号',
@ -341,7 +343,7 @@ class Pages extends Controller
$effective = 'COALESCE(difference, ' . self::receiptNetSql() . ')';
// 同一交易单号在相邻月份的收入/优惠/冲减合并后与订单实付对平时,
// 这是最能解释单月差额的原因,必须排在其它逐月规则之前。
$rules['差额=跨月收款(合并已对平)'] = 'cross_month_balanced = 1';
$rules[self::RECEIPT_CROSS_MONTH_REMARK] = 'cross_month_balanced = 1';
$rules[$label . '未匹配'] = "match_status = '未匹配'";
$rules[$label . '取数为0(种菜)'] = "match_status = '已匹配' AND planting_amount IS NOT NULL AND ABS(COALESCE($column, 0)) < 0.005";
$rules[$label . '取数为0'] = "match_status = '已匹配' AND planting_amount IS NULL AND ABS(COALESCE($column, 0)) < 0.005";
@ -658,7 +660,7 @@ class Pages extends Controller
}
if ((int) ($row->cross_month_balanced ?? 0) === 1) {
return '差额=跨月收款(合并已对平)';
return self::RECEIPT_CROSS_MONTH_REMARK;
}
if (($row->match_status ?? '') === '未匹配') {
@ -1422,11 +1424,12 @@ class Pages extends Controller
$platformFilter = $this->receiptOrderMatchFilterValues(request('platform'));
$storeFilter = $this->receiptOrderMatchFilterValues(request('store_name'));
[$statusFilter, $remarkFilter] = $this->receiptOrderMatchStatusFilters(request('match_status'), request('remark'));
$crossMonthOnly = self::isReceiptCrossMonthOnlyFilter($remarkFilter);
$unmatchedReasonFilter = $this->receiptOrderMatchFilterValues(request('unmatched_reason'));
$differenceState = $this->receiptOrderMatchFilterValues(request('difference_state'));
$orderNo = trim((string) request('online_order_no', ''));
// 闭包里要用 $this->applyReceiptRemarkFilter()(差异原因备注按公式判定),所以不能声明为 static
$applyFilters = function ($query) use ($platformFilter, $storeFilter, $orderNo, $statusFilter, $remarkFilter, $unmatchedReasonFilter, $differenceState): void {
$applyFilters = function ($query) use ($platformFilter, $storeFilter, $orderNo, $statusFilter, $remarkFilter, $crossMonthOnly, $unmatchedReasonFilter, $differenceState): void {
if ($platformFilter !== []) {
$query->whereIn('platform', $platformFilter);
}
@ -1447,7 +1450,7 @@ class Pages extends Controller
$this->applyReceiptUnmatchedReasonFilter($query, $unmatchedReasonFilter);
}
if ($differenceState !== []) {
$this->applyReceiptDifferenceStateFilter($query, $differenceState);
$this->applyReceiptDifferenceStateFilter($query, $differenceState, $crossMonthOnly);
}
};
@ -1482,7 +1485,7 @@ class Pages extends Controller
")->first();
$total = (int) ($agg->total ?? 0);
$differenceTotal = (float) ($agg->difference_base ?? 0);
if ((int) ($agg->basis_fallback_rows ?? 0) > 0) {
if (! $crossMonthOnly && (int) ($agg->basis_fallback_rows ?? 0) > 0) {
$adjustmentQuery = $base();
$applyFilters($adjustmentQuery);
$adjustment = $adjustmentQuery
@ -1638,7 +1641,9 @@ class Pages extends Controller
}
$query = $this->receiptOrderMatchFilteredQuery($month);
$total = $this->receiptOrderMatchMetricTotal($query, $metric);
[, $remarkFilter] = $this->receiptOrderMatchStatusFilters(null, request('remark'));
$crossMonthOnly = self::isReceiptCrossMonthOnlyFilter($remarkFilter);
$total = $this->receiptOrderMatchMetricTotal($query, $metric, $crossMonthOnly);
$connection = (string) config('upstream_mirrors.connection', 'business_mirror');
$detailMetric = $definition['metric'];
@ -1781,6 +1786,7 @@ class Pages extends Controller
request('match_status'),
request('remark'),
);
$crossMonthOnly = self::isReceiptCrossMonthOnlyFilter($remarkFilter);
$unmatchedReasonFilter = $this->receiptOrderMatchFilterValues(request('unmatched_reason'));
$differenceState = $this->receiptOrderMatchFilterValues(request('difference_state'));
$orderNo = trim((string) request('online_order_no', ''));
@ -1809,13 +1815,13 @@ class Pages extends Controller
$this->applyReceiptUnmatchedReasonFilter($query, $unmatchedReasonFilter);
}
if ($differenceState !== []) {
$this->applyReceiptDifferenceStateFilter($query, $differenceState);
$this->applyReceiptDifferenceStateFilter($query, $differenceState, $crossMonthOnly);
}
return $query;
}
private function receiptOrderMatchMetricTotal($query, string $metric): float
private function receiptOrderMatchMetricTotal($query, string $metric, bool $crossMonthOnly = false): float
{
if ($metric !== 'difference') {
return round((float) (clone $query)->sum($metric), 2);
@ -1827,7 +1833,7 @@ class Pages extends Controller
. "SUM(CASE WHEN remark IN ({$fallbackSql}) THEN 1 ELSE 0 END) as basis_fallback_rows"
)->first();
$total = (float) ($row->difference_base ?? 0);
if ((int) ($row->basis_fallback_rows ?? 0) > 0) {
if (! $crossMonthOnly && (int) ($row->basis_fallback_rows ?? 0) > 0) {
$adjustment = (clone $query)
->whereIn('remark', self::RECEIPT_BASIS_FALLBACK_REMARKS)
->selectRaw('SUM(' . self::receiptFallbackDifferenceAdjustmentSql() . ') as adjustment')
@ -1851,20 +1857,44 @@ class Pages extends Controller
}
$labels = self::receiptDifferenceRemarkLabels();
$crossMonthSelected = in_array(self::RECEIPT_CROSS_MONTH_REMARK, $remarkFilter, true);
$indexes = [];
foreach ($remarkFilter as $label) {
if ($label === self::RECEIPT_CROSS_MONTH_REMARK) {
continue;
}
$index = array_search($label, $labels, true);
if ($index !== false) {
$indexes[] = (int) $index + 1;
}
}
if ($indexes === []) {
if (! $crossMonthSelected && $indexes === []) {
return;
}
$query->whereRaw(
self::receiptDifferenceRemarkIndexSql() . ' IN (' . implode(', ', array_unique($indexes)) . ')'
);
if ($crossMonthSelected && $indexes === []) {
$query->where('cross_month_balanced', 1);
return;
}
$query->where(function ($group) use ($crossMonthSelected, $indexes): void {
if ($crossMonthSelected) {
$group->where('cross_month_balanced', 1);
}
if ($indexes !== []) {
$condition = self::receiptDifferenceRemarkIndexSql()
. ' IN (' . implode(', ', array_unique($indexes)) . ')';
$crossMonthSelected ? $group->orWhereRaw($condition) : $group->whereRaw($condition);
}
});
}
/** @param list<string> $remarkFilter */
private static function isReceiptCrossMonthOnlyFilter(array $remarkFilter): bool
{
return count($remarkFilter) === 1
&& $remarkFilter[0] === self::RECEIPT_CROSS_MONTH_REMARK;
}
/**
@ -1900,7 +1930,7 @@ class Pages extends Controller
* @param \Illuminate\Database\Query\Builder $query
* @param string|list<string> $state
*/
private function applyReceiptDifferenceStateFilter($query, $state): void
private function applyReceiptDifferenceStateFilter($query, $state, bool $crossMonthOnly = false): void
{
$states = is_array($state) ? $state : [$state];
$has = in_array('有差额', $states, true);
@ -1908,7 +1938,11 @@ class Pages extends Controller
if ($has === $none) {
return;
}
$effective = self::receiptEffectiveDifferenceSql();
// 已限定跨月第一优先级时,跨月行不会再落入口径兜底归零,
// “有/无差额”可直接使用默认差额,避免整串原因 CASE 重复执行。
$effective = $crossMonthOnly
? self::receiptDefaultDifferenceSql()
: self::receiptEffectiveDifferenceSql();
$query->whereRaw($has ? 'ABS(' . $effective . ') > 0.005' : 'ABS(' . $effective . ') <= 0.005');
}
@ -1921,6 +1955,9 @@ class Pages extends Controller
*/
private function receiptDifferenceStateFacet(string $month, array $filters, string $orderNo): array
{
$crossMonthOnly = self::isReceiptCrossMonthOnlyFilter(
array_values((array) ($filters['remark'] ?? []))
);
$query = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror'))
->table('store_receipt_order_match_rows')
->where('report_month', $month)
@ -1944,7 +1981,9 @@ class Pages extends Controller
$query->where('online_order_no', 'like', '%' . addcslashes($orderNo, '%_\\') . '%');
}
$effective = self::receiptEffectiveDifferenceSql();
$effective = $crossMonthOnly
? self::receiptDefaultDifferenceSql()
: self::receiptEffectiveDifferenceSql();
$row = $query->selectRaw('SUM(CASE WHEN ABS(' . $effective . ') > 0.005 THEN 1 ELSE 0 END) as has_diff, '
. 'SUM(CASE WHEN ABS(' . $effective . ') <= 0.005 THEN 1 ELSE 0 END) as none_diff')->first();
@ -2253,6 +2292,9 @@ class Pages extends Controller
*/
private function receiptOrderMatchFacet(string $month, string $column, array $filters, string $orderNo, array $order = []): array
{
$crossMonthOnly = self::isReceiptCrossMonthOnlyFilter(
array_values((array) ($filters['remark'] ?? []))
);
$query = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror'))
->table('store_receipt_order_match_rows')
->where('report_month', $month);
@ -2273,7 +2315,7 @@ class Pages extends Controller
}
// 差额(有/无):按页面差额口径过滤
if ($key === 'difference_state') {
$this->applyReceiptDifferenceStateFilter($query, (array) $values);
$this->applyReceiptDifferenceStateFilter($query, (array) $values, $crossMonthOnly);
continue;
}
$query->whereIn($key, $values);
@ -2437,6 +2479,7 @@ class Pages extends Controller
}
$statusFilter = $this->receiptOrderMatchFilterValues($request->query('match_status'));
[$statusValues, $remarkValues] = $this->receiptOrderMatchStatusFilters($statusFilter, $request->query('remark'));
$crossMonthOnly = self::isReceiptCrossMonthOnlyFilter($remarkValues);
if ($statusValues !== []) {
$query->whereIn('match_status', $statusValues);
}
@ -2449,7 +2492,7 @@ class Pages extends Controller
}
$differenceState = $this->receiptOrderMatchFilterValues($request->query('difference_state'));
if ($differenceState !== []) {
$this->applyReceiptDifferenceStateFilter($query, $differenceState);
$this->applyReceiptDifferenceStateFilter($query, $differenceState, $crossMonthOnly);
}
// 「订单实付含已退款部分」「订单实付未减联合营销费」判定所需证据列(与页面同一口径;仅对应平台负差额行会真正计算子查询)。

View File

@ -3,6 +3,7 @@
namespace Tests\Unit\EcommerceFinance;
use App\Http\Controllers\Business\Pages;
use Illuminate\Support\Facades\DB;
use ReflectionMethod;
use Tests\TestCase;
@ -74,6 +75,25 @@ class PerformanceDifferenceRemarkTest extends TestCase
]));
}
public function test_cross_month_filters_use_direct_columns_without_recomputing_reason_case(): void
{
$controller = app(Pages::class);
$query = DB::connection('business_mirror')->table('store_receipt_order_match_rows');
$remarkFilter = new ReflectionMethod(Pages::class, 'applyReceiptRemarkFilter');
$remarkFilter->setAccessible(true);
$remarkFilter->invoke($controller, $query, ['差额=跨月收款(合并已对平)']);
$this->assertStringContainsString('cross_month_balanced', $query->toSql());
$this->assertStringNotContainsString('CASE WHEN', $query->toSql());
$differenceFilter = new ReflectionMethod(Pages::class, 'applyReceiptDifferenceStateFilter');
$differenceFilter->setAccessible(true);
$differenceFilter->invoke($controller, $query, ['有差额'], true);
$this->assertStringNotContainsString('CASE WHEN', $query->toSql());
}
public function test_pinduoduo_partial_refund_marks_platform_payment_discount(): void
{
$this->assertSame('差额=平台支付立减', $this->remark([