店铺收款账户账单:总账备注优先取「科目映射表」的财务备注

账单项目在「科目映射表」里能匹到(同平台+同项目)时,总账备注取该规则的「财务备注」;
多规则时按业务备注/商品名称关键词取最具体的一条;匹不到则保留原有总账备注映射。规则按平台+项目预索引,避免逐行扫全表。
This commit is contained in:
qiuxiang 2026-10-07 15:24:58 +08:00
parent db2ee47fd8
commit 1cbc8312a6

View File

@ -17,6 +17,9 @@ class AccountLedgerRemarkMap
{
public const CACHE_KEY = 'ec_account_ledger_remarks_map_v1';
/** 「平台 + 账单项目」→ 科目映射表规则(请求内缓存,避免逐行扫描全部规则)。 */
private ?array $accountMappingIndex = null;
/** 页签默认平台(店铺名没有平台前缀时使用)。 */
private const TAB_PLATFORM = [
'taobao-alipay' => '淘宝',
@ -241,20 +244,44 @@ class AccountLedgerRemarkMap
return $tabPlatform;
}
/** 查总账备注;没有匹配返回 null。 */
public function remark(?string $storeName, ?string $tabPlatform, ?string $billItem): ?string
/**
* 查总账备注;没有匹配返回 null。
*
* 取值顺序:
* ①「财务规则 › 科目映射表」里同平台同账单项目的「财务备注」(匹配器按业务备注/商品名称关键词取最具体的一条)
* ② 上面没匹到时,回落到本表(账单项目 → 总账备注)原有的口径
*/
public function remark(
?string $storeName,
?string $tabPlatform,
?string $billItem,
array $businessRemarks = [],
string $goodsName = '',
): ?string
{
$item = trim((string) $billItem);
if ($item === '') {
return null;
}
$platforms = [$this->platform($storeName, $tabPlatform), $tabPlatform];
foreach ($platforms as $platform) {
if ($platform === null || $platform === '') {
continue;
}
$fromRule = $this->accountMappingRemark($platform, $item, $businessRemarks, $goodsName);
if ($fromRule !== null) {
return $fromRule;
}
}
$map = $this->map();
if ($map === []) {
return null;
}
foreach ([$this->platform($storeName, $tabPlatform), $tabPlatform] as $platform) {
foreach ($platforms as $platform) {
if ($platform === null || $platform === '') {
continue;
}
@ -266,6 +293,59 @@ class AccountLedgerRemarkMap
return null;
}
/** 科目映射表按「平台 + 账单项目」预索引,避免逐行扫全部规则。 */
private function accountMappingIndex(): array
{
if ($this->accountMappingIndex !== null) {
return $this->accountMappingIndex;
}
$index = [];
try {
foreach (app(AccountMappingStore::class)->all() as $rule) {
if (! is_array($rule)) {
continue;
}
$platform = trim((string) ($rule['platform'] ?? ''));
$item = trim((string) ($rule['item'] ?? ''));
if ($platform === '' || $item === '') {
continue;
}
$index[$platform][$item][] = $rule;
}
} catch (Throwable) {
$index = [];
}
return $this->accountMappingIndex = $index;
}
/** 同平台同账单项目的「财务备注」;没有规则或财务备注为空时返回 null(由调用方回落)。 */
private function accountMappingRemark(string $platform, string $item, array $businessRemarks, string $goodsName): ?string
{
$rules = $this->accountMappingIndex()[$platform][$item] ?? [];
if ($rules === []) {
return null;
}
if (count($rules) === 1) {
$remark = trim((string) ($rules[0]['memo'] ?? ''));
return $remark === '' ? null : $remark;
}
// 同一账单项目有多条规则时,用映射表自己的匹配器(按商品名称/业务备注关键词取最具体的)
try {
$result = app(AccountMappingMatcher::class)->match($platform, $item, $businessRemarks, $goodsName);
} catch (Throwable) {
return null;
}
$remark = trim((string) ($result['rule']['memo'] ?? ''));
return $remark === '' ? null : $remark;
}
/**
* 给一行账单补上 payload.ledger_remark(内部字段名 ledger_remark)。
*
@ -279,6 +359,8 @@ class AccountLedgerRemarkMap
$row['store_name'] ?? null,
$tabPlatform,
$row['bill_item'] ?? null,
$this->businessRemarks($row),
$this->goodsName($row),
);
if ($remark === null) {
return $row;
@ -293,6 +375,53 @@ class AccountLedgerRemarkMap
return $row;
}
/**
* 账单上的业务备注(用于科目映射表的「业务备注关键词」条件)。
*
* @param array<string,mixed> $row
* @return list<string>
*/
private function businessRemarks(array $row): array
{
$payload = is_array($row['payload'] ?? null) ? $row['payload'] : [];
$values = [
$row['custom_remark'] ?? null,
$row['finance_remark'] ?? null,
$payload['second_remark'] ?? null,
$payload['business_description'] ?? null,
$payload['move_summary'] ?? null,
$payload['settle_remark'] ?? null,
$payload['tx_remark'] ?? null,
$payload['cost_name'] ?? null,
];
$remarks = [];
foreach ($values as $value) {
$text = trim((string) $value);
if ($text !== '') {
$remarks[] = $text;
}
}
return $remarks;
}
/** 商品名称(用于科目映射表的「商品名称关键词」条件)。 */
private function goodsName(array $row): string
{
$payload = is_array($row['payload'] ?? null) ? $row['payload'] : [];
foreach (['goods_name', 'product_name', 'goods_title'] as $key) {
$value = trim((string) ($payload[$key] ?? ''));
if ($value !== '') {
return $value;
}
}
return '';
}
/**
* @param iterable<int,array<string,mixed>> $rows
* @return \Generator<int,array<string,mixed>>