CaiWuXiTong/modules/EcommerceFinance/Services/AccountLedgerRemarkMap.php
qiuxiang d76b102bca 总账备注平台口径:1688 / 新佰广 前缀按阿里巴巴匹配
新佰广1688、1688xxx 店铺名不带「阿里巴巴」前缀,之前落到页签默认平台(淘系支付宝=淘宝),
1688 系账单项目匹配不到总账备注;新增两条前缀映射(1688、新佰广 → 阿里巴巴)并补单测断言。
2026-10-07 13:20:06 +08:00

307 lines
10 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\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Throwable;
/**
* 「总账备注」取值:按(平台 + 账单项目)匹配 ec_account_ledger_remarks。
*
* 店铺收款账户账单各页签的账单项目名称不一致,平台口径以店铺名前缀为准
* (如「抖音-xxx」「天猫-xxx」),页签默认平台只作为兜底,避免淘系页签里
* 天猫/淘宝/阿里巴巴三类项目混用同一份映射。
*/
class AccountLedgerRemarkMap
{
public const CACHE_KEY = 'ec_account_ledger_remarks_map_v1';
/** 页签默认平台(店铺名没有平台前缀时使用)。 */
private const TAB_PLATFORM = [
'taobao-alipay' => '淘宝',
'taobao-wechat' => '淘宝',
'pinduoduo' => '拼多多',
'jd-transactions' => '京东',
'jingxi' => '京喜',
'jd-wallet' => '京东',
'douyin' => '抖音',
'dewu' => '得物',
'vipshop' => '唯品会MP',
'vipshop-jx' => '唯品会jx',
];
/**
* 页签 → 该页签可能出现的平台。
*
* 大部分页签只有一个平台;淘系支付宝与淘系微信账户同一页签内混有
* 天猫/淘宝/阿里巴巴的原始店铺,需要按店铺名前缀再分平台。
*/
private const TAB_PLATFORMS = [
'taobao-alipay' => ['淘宝', '天猫', '阿里巴巴'],
'taobao-wechat' => ['淘宝', '天猫'],
'pinduoduo' => ['拼多多'],
'jd-transactions' => ['京东'],
'jingxi' => ['京喜'],
'jd-wallet' => ['京东'],
'douyin' => ['抖音'],
'dewu' => ['得物'],
'vipshop' => ['唯品会MP'],
'vipshop-jx' => ['唯品会jx'],
];
/** 店铺名前缀可识别的平台(顺序敏感:先长后短)。 */
private const STORE_PREFIX_PLATFORMS = [
'唯品会JX' => '唯品会jx',
'唯品会MP' => '唯品会MP',
'阿里巴巴' => '阿里巴巴',
// 1688 系店铺:新佰广1688 / 1688xxx 等店铺名不带「阿里巴巴」前缀,
// 按平台口径归到阿里巴巴,账单项目与阿里巴巴一致(阿里-xxx 仍走上面的前缀)。
'1688' => '阿里巴巴',
'新佰广' => '阿里巴巴',
'拼多多' => '拼多多',
'天猫' => '天猫',
'淘宝' => '淘宝',
'抖音' => '抖音',
'得物' => '得物',
'京喜' => '京喜',
'京东' => '京东',
];
/**
* @return array<string,array<string,string>> [platform => [bill_item => ledger_remark]]
*/
public function map(): array
{
return Cache::rememberForever(self::CACHE_KEY, function (): array {
try {
if (! Schema::hasTable('ec_account_ledger_remarks')) {
return [];
}
$map = [];
foreach (\Illuminate\Support\Facades\DB::table('ec_account_ledger_remarks')
->select(['platform', 'bill_item', 'ledger_remark'])
->get() as $row) {
$platform = trim((string) $row->platform);
$item = trim((string) $row->bill_item);
if ($platform === '' || $item === '') {
continue;
}
$map[$platform][$item] = (string) $row->ledger_remark;
}
return $map;
} catch (Throwable) {
return [];
}
});
}
public function forget(): void
{
Cache::forget(self::CACHE_KEY);
}
/** 页签默认平台。 */
public function defaultPlatform(string $tab): ?string
{
return self::TAB_PLATFORM[$tab] ?? null;
}
/** 页签涉及的平台(筛选下拉与账单项目翻译共用)。 */
public function platformsForTab(string $tab): array
{
if (isset(self::TAB_PLATFORMS[$tab])) {
return self::TAB_PLATFORMS[$tab];
}
$default = $this->defaultPlatform($tab);
return $default === null ? [] : [$default];
}
/** 平台 → 店铺名前缀(一个平台可能对应多个前缀)。 */
private function prefixesForPlatform(string $platform): array
{
$prefixes = [];
foreach (self::STORE_PREFIX_PLATFORMS as $prefix => $candidate) {
if ($candidate === $platform) {
$prefixes[] = $prefix;
}
}
return $prefixes;
}
/** 页签可选的「总账备注」(该页签涉及平台的去重并集)。 */
public function remarksForTab(string $tab): array
{
$map = $this->map();
$remarks = [];
foreach ($this->platformsForTab($tab) as $platform) {
foreach (array_values($map[$platform] ?? []) as $remark) {
$remark = trim((string) $remark);
if ($remark !== '') {
$remarks[$remark] = true;
}
}
}
$list = array_keys($remarks);
sort($list, SORT_NATURAL | SORT_FLAG_CASE);
return $list;
}
/**
* 「总账备注」→ 按平台分组的账单项目集合。
*
* 单平台页签只有一组、且不做店铺名前缀限制,可直接 whereIn(bill_item, items);
* 多平台页签(淘系支付宝 / 淘系微信)必须连店铺名前缀一起过滤,才能与页面上
* 按「店铺名前缀 → 平台 + 账单项目」逐行取到的总账备注完全一致。
*
* @return list<array{platform:string,prefixes:list<string>,excludePrefixes:list<string>,items:list<string>,isDefault:bool}>
*/
public function billItemGroupsForTab(string $tab, ?string $remark): array
{
$want = trim((string) $remark);
if ($want === '') {
return [];
}
$map = $this->map();
if ($map === []) {
return [];
}
$platforms = $this->platformsForTab($tab);
$single = count($platforms) <= 1;
$default = $this->defaultPlatform($tab);
$known = array_keys(self::STORE_PREFIX_PLATFORMS);
$groups = [];
foreach ($platforms as $platform) {
$items = [];
foreach ($map[$platform] ?? [] as $item => $value) {
if (trim((string) $value) === $want) {
$items[] = (string) $item;
}
}
if ($items === []) {
continue;
}
sort($items);
$prefixes = $single ? [] : $this->prefixesForPlatform($platform);
$isDefault = ! $single && $platform === $default;
$groups[] = [
'platform' => $platform,
// 单平台页签不做店铺名前缀限制(页签本身就是该平台口径)。
'prefixes' => $prefixes,
// 默认平台还要包含「没有已知平台前缀」的店铺。
'excludePrefixes' => $isDefault ? array_values(array_diff($known, $prefixes)) : [],
'items' => $items,
'isDefault' => $isDefault,
];
}
return $groups;
}
/** 「总账备注」→ 该页签全部相关账单项目(多平台页签为各平台并集)。 */
public function billItemsForTab(string $tab, ?string $remark): array
{
$items = [];
foreach ($this->billItemGroupsForTab($tab, $remark) as $group) {
foreach ($group['items'] as $item) {
$items[$item] = true;
}
}
$list = array_keys($items);
sort($list);
return $list;
}
/** 解析某一行账单所属平台:优先店铺名前缀,其次页签默认。 */
public function platform(?string $storeName, ?string $tabPlatform): ?string
{
$name = trim((string) $storeName);
if ($name !== '') {
foreach (self::STORE_PREFIX_PLATFORMS as $prefix => $platform) {
if (str_starts_with($name, $prefix)) {
return $platform;
}
}
}
return $tabPlatform;
}
/** 查总账备注;没有匹配返回 null。 */
public function remark(?string $storeName, ?string $tabPlatform, ?string $billItem): ?string
{
$item = trim((string) $billItem);
if ($item === '') {
return null;
}
$map = $this->map();
if ($map === []) {
return null;
}
foreach ([$this->platform($storeName, $tabPlatform), $tabPlatform] as $platform) {
if ($platform === null || $platform === '') {
continue;
}
if (isset($map[$platform][$item])) {
return $map[$platform][$item];
}
}
return null;
}
/**
* 给一行账单补上 payload.ledger_remark(内部字段名 ledger_remark)。
*
* @param array<string,mixed> $row
* @return array<string,mixed>
*/
public function decorate(array $row, string $tab): array
{
$tabPlatform = $this->defaultPlatform($tab);
$remark = $this->remark(
$row['store_name'] ?? null,
$tabPlatform,
$row['bill_item'] ?? null,
);
if ($remark === null) {
return $row;
}
$payload = is_array($row['payload'] ?? null) ? $row['payload'] : [];
$payload['ledger_remark'] = $remark;
$row['payload'] = $payload;
// 淘系支付宝页签按顶层字段取值,这里同时补一份,页面与导出共用。
$row['ledger_remark'] = $remark;
return $row;
}
/**
* @param iterable<int,array<string,mixed>> $rows
* @return \Generator<int,array<string,mixed>>
*/
public function decorateAll(iterable $rows, string $tab): \Generator
{
foreach ($rows as $row) {
yield is_array($row) ? $this->decorate($row, $tab) : $row;
}
}
}