diff --git a/modules/EcommerceFinance/View/BillingDetailsPageData.php b/modules/EcommerceFinance/View/BillingDetailsPageData.php index c23d5496..815740b6 100644 --- a/modules/EcommerceFinance/View/BillingDetailsPageData.php +++ b/modules/EcommerceFinance/View/BillingDetailsPageData.php @@ -737,22 +737,10 @@ class BillingDetailsPageData $conditions[] = '(' . implode(' OR ', $haystack) . ')'; } if ($filters['type'] !== []) { - $allowed = $this->itemsForTypes($filters['type']); - if ($allowed === []) { - $conditions[] = '1 = 0'; - } else { - // 与页面带出财务备注/类型时一致:wx 前缀的账单项目按去前缀后的名称归类。 - // 项目名以字面量内联:MySQL 对字面量 IN 列表会排序后二分查找, - // 参数化 IN 会逐行线性比对,161 个项目 × 130 万行会退化到 30 秒以上。 - $conditions[] = $this->normalizedItemSql() . ' IN (' . $this->literals($allowed) . ')'; - } + $conditions[] = $this->platformItemCondition('category', $filters['type'], $label); } if (($filters['remark'] ?? []) !== []) { - // 财务备注筛选:与财务类型同一口径,先落回「账单项目」集合再下推 SQL。 - $allowed = $this->itemsForRemarks($filters['remark']); - $conditions[] = $allowed === [] - ? '1 = 0' - : $this->normalizedItemSql() . ' IN (' . $this->literals($allowed) . ')'; + $conditions[] = $this->platformItemCondition('memo', $filters['remark'], $label); } $dateBind = $dateCol === 'bill_date' @@ -902,6 +890,57 @@ class BillingDetailsPageData return "(CASE WHEN bill_item LIKE 'wx%' THEN LTRIM(SUBSTRING(bill_item, 3)) ELSE bill_item END)"; } + /** + * 「财务类型 / 财务备注」→ 按平台限定的 SQL 条件。 + * + * 同一账单项目在不同平台可能挂不同的财务备注/类型,只按项目名过滤会把其它平台的同名项目一起带出来, + * 所以这里再按店铺前缀限定平台;项目名仍以字面量内联,避免参数化 IN 在千万行上退化成线性比对。 + * + * @param string $field category(财务类型)或 memo(财务备注) + * @param list $values + */ + private function platformItemCondition(string $field, array $values, string $branchLabel): string + { + $wanted = array_flip($values); + $byPlatform = []; + foreach ($this->rules() as $key => $rule) { + $value = (string) ($rule[$field] ?? ''); + if ($value === '' || ! isset($wanted[$value])) { + continue; + } + [$platform, $item] = array_pad(explode('|', $key, 2), 2, ''); + if ($platform === '' || $item === '') { + continue; + } + $byPlatform[$platform][$item] = true; + } + + $branchPlatforms = self::BRANCH_PLATFORMS[$branchLabel] ?? []; + $parts = []; + foreach ($byPlatform as $platform => $items) { + if ($branchPlatforms !== [] && ! in_array($platform, $branchPlatforms, true)) { + continue; + } + $parts[] = '(' . $this->storePrefixCondition($platform) . ' AND ' + . $this->normalizedItemSql() . ' IN (' . $this->literals(array_keys($items)) . '))'; + } + + return $parts === [] ? '1 = 0' : '(' . implode(' OR ', $parts) . ')'; + } + + /** 科目映射表平台 → 对应的店铺名前缀条件(阿里巴巴含新佰广1688 / 淘工厂等)。 */ + private function storePrefixCondition(string $platform): string + { + $parts = []; + foreach (self::PLATFORM_PREFIX as $prefix => $mapped) { + if ($mapped === $platform) { + $parts[] = 'store_name LIKE ' . $this->likeLiteral($prefix); + } + } + + return $parts === [] ? '1 = 1' : '(' . implode(' OR ', $parts) . ')'; + } + /** * 取与查询区间有交集的账期的 active_generation(与 JD / 抖音账单页仓储同一口径)。 *