668 lines
23 KiB
PHP
668 lines
23 KiB
PHP
<?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 ?array $accountMappingIndex = null;
|
||
|
||
/** 页签默认平台(店铺名没有平台前缀时使用)。 */
|
||
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'],
|
||
];
|
||
|
||
/** 页签账单表里可作「业务备注关键词」匹配的列(折算筛选 SQL 用;列不存在时自动跳过)。 */
|
||
private const TAB_REMARK_COLUMNS = [
|
||
'taobao-alipay' => ['custom_remark', 'business_type', 'remark', 'second_remark'],
|
||
'taobao-wechat' => ['custom_remark', 'remark'],
|
||
'pinduoduo' => ['custom_remark', 'business_type', 'remark'],
|
||
'dewu' => ['custom_remark', 'business_type', 'order_type', 'channel'],
|
||
'jd-transactions' => ['custom_remark'],
|
||
'jingxi' => ['custom_remark'],
|
||
'jd-wallet' => ['custom_remark'],
|
||
'douyin' => [],
|
||
'vipshop' => [],
|
||
'vipshop-jx' => [],
|
||
];
|
||
|
||
/** 页签账单表里可作「商品名称关键词」匹配的列。 */
|
||
private const TAB_GOODS_COLUMNS = [
|
||
'taobao-alipay' => ['goods_name'],
|
||
'dewu' => ['goods_name'],
|
||
];
|
||
|
||
/** 店铺名前缀可识别的平台(顺序敏感:先长后短)。 */
|
||
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 = [];
|
||
$rulesByPlatform = $this->accountMappingRulesByPlatform();
|
||
foreach ($this->platformsForTab($tab) as $platform) {
|
||
foreach (array_values($map[$platform] ?? []) as $remark) {
|
||
$remark = trim((string) $remark);
|
||
if ($remark !== '') {
|
||
$remarks[$remark] = true;
|
||
}
|
||
}
|
||
// 「财务规则 › 科目映射表」里的财务备注同样作为候选,
|
||
// 否则新加的备注要等总账备注映射表同步后才能在下拉里选到。
|
||
foreach ($rulesByPlatform[$this->normalizeKey($platform)] ?? [] as $rules) {
|
||
foreach ($rules as $rule) {
|
||
$memo = trim((string) ($rule['memo'] ?? ''));
|
||
if ($memo !== '') {
|
||
$remarks[$memo] = 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();
|
||
$platforms = $this->platformsForTab($tab);
|
||
$single = count($platforms) <= 1;
|
||
$default = $this->defaultPlatform($tab);
|
||
$known = array_keys(self::STORE_PREFIX_PLATFORMS);
|
||
$rulesByPlatform = $this->accountMappingRulesByPlatform();
|
||
$remarkColumns = self::TAB_REMARK_COLUMNS[$tab] ?? [];
|
||
$goodsColumns = self::TAB_GOODS_COLUMNS[$tab] ?? [];
|
||
|
||
$groups = [];
|
||
foreach ($platforms as $platform) {
|
||
$platformKey = $this->normalizeKey($platform);
|
||
$items = [];
|
||
$conditions = [];
|
||
$this->fillPlatformPlan($platform, $platformKey, $want, $rulesByPlatform, $map, $items, $conditions);
|
||
|
||
if ($items === [] && $conditions === []) {
|
||
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,
|
||
// 同一账单项目对应多条规则时,用关键词条件折算(LedgerRemarkFilter 负责拼 SQL)
|
||
'conditions' => $conditions,
|
||
'remarkColumns' => $remarkColumns,
|
||
'goodsColumns' => $goodsColumns,
|
||
'isDefault' => $isDefault,
|
||
];
|
||
}
|
||
|
||
return $groups;
|
||
}
|
||
|
||
/**
|
||
* 单个平台的「总账备注」折算素材:直接项目 + 关键词条件(账单明细等多分支页面用)。
|
||
*
|
||
* @return array{items:list<string>,conditions:list<array<string,mixed>>}
|
||
*/
|
||
public function ledgerRemarkPlanForPlatform(string $platform, string $remark): array
|
||
{
|
||
$want = trim($remark);
|
||
if ($want === '') {
|
||
return ['items' => [], 'conditions' => []];
|
||
}
|
||
|
||
$items = [];
|
||
$conditions = [];
|
||
$this->fillPlatformPlan(
|
||
$platform,
|
||
$this->normalizeKey($platform),
|
||
$want,
|
||
$this->accountMappingRulesByPlatform(),
|
||
$this->map(),
|
||
$items,
|
||
$conditions,
|
||
);
|
||
|
||
return ['items' => $items, 'conditions' => $conditions];
|
||
}
|
||
|
||
/**
|
||
* 单个平台的折算逻辑:科目映射表优先(按关键词区分),没覆盖的项目回落老「总账备注映射表」。
|
||
*
|
||
* @param array<string,array<string,list<array<string,mixed>>>> $rulesByPlatform
|
||
* @param array<string,array<string,string>> $map
|
||
* @param list<string> $items
|
||
* @param list<array<string,mixed>> $conditions
|
||
*/
|
||
private function fillPlatformPlan(
|
||
string $platform,
|
||
string $platformKey,
|
||
string $want,
|
||
array $rulesByPlatform,
|
||
array $map,
|
||
array &$items,
|
||
array &$conditions,
|
||
): void {
|
||
foreach ($rulesByPlatform[$platformKey] ?? [] as $item => $rules) {
|
||
if (count($rules) === 1) {
|
||
if (trim((string) ($rules[0]['memo'] ?? '')) === $want) {
|
||
$items[] = (string) $item;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
$keywordConditions = [];
|
||
$fallback = null;
|
||
foreach ($rules as $rule) {
|
||
$memo = trim((string) ($rule['memo'] ?? ''));
|
||
$goods = trim((string) ($rule['goods'] ?? ''));
|
||
if ($memo === '' && $goods === '') {
|
||
$fallback ??= $rule;
|
||
continue;
|
||
}
|
||
$keywordConditions[] = ['memo' => $memo, 'goods' => $goods];
|
||
}
|
||
$fallback ??= $rules[0];
|
||
|
||
foreach ($keywordConditions as $condition) {
|
||
if ($condition['memo'] !== '' && $condition['memo'] === $want) {
|
||
$conditions[] = [
|
||
'item' => (string) $item,
|
||
'memo' => $condition['memo'],
|
||
'goods' => $condition['goods'],
|
||
'negate' => false,
|
||
'others' => [],
|
||
];
|
||
}
|
||
}
|
||
if (trim((string) ($fallback['memo'] ?? '')) === $want) {
|
||
// 兜底规则:命不中任何关键词的行才归它
|
||
$conditions[] = [
|
||
'item' => (string) $item,
|
||
'memo' => '',
|
||
'goods' => '',
|
||
'negate' => true,
|
||
'others' => $keywordConditions,
|
||
];
|
||
}
|
||
}
|
||
|
||
foreach ($map[$platform] ?? [] as $item => $value) {
|
||
if (isset($rulesByPlatform[$platformKey][$item])) {
|
||
continue;
|
||
}
|
||
if (trim((string) $value) === $want) {
|
||
$items[] = (string) $item;
|
||
}
|
||
}
|
||
|
||
$items = array_values(array_unique($items));
|
||
sort($items);
|
||
}
|
||
|
||
/**
|
||
* 科目映射表按「平台 → 账单项目 → 规则」预索引(平台名做大小写/空格归一)。
|
||
*
|
||
* @return array<string,array<string,list<array<string,mixed>>>>
|
||
*/
|
||
private function accountMappingRulesByPlatform(): array
|
||
{
|
||
$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[$this->normalizeKey($platform)][$item][] = $rule;
|
||
}
|
||
} catch (Throwable) {
|
||
return [];
|
||
}
|
||
|
||
return $index;
|
||
}
|
||
|
||
/** 「总账备注」→ 该页签全部相关账单项目(多平台页签为各平台并集)。 */
|
||
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,
|
||
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 ($platforms as $platform) {
|
||
if ($platform === null || $platform === '') {
|
||
continue;
|
||
}
|
||
// 先按原样匹配,再按「去掉 wx 前缀」的账单项目匹配(淘系部分行带 wx 前缀)
|
||
foreach ($this->itemLookupKeys($item) as $candidate) {
|
||
if (isset($map[$platform][$candidate])) {
|
||
return $map[$platform][$candidate];
|
||
}
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
// 平台/账单项目做大小写与空格归一:映射表里写的是「唯品会JX」,页签内部键是「唯品会jx」
|
||
$index[$this->normalizeKey($platform)][$this->normalizeKey($item)][] = $rule;
|
||
}
|
||
} catch (Throwable) {
|
||
$index = [];
|
||
}
|
||
|
||
return $this->accountMappingIndex = $index;
|
||
}
|
||
|
||
/** 同平台同账单项目的「财务备注」;没有规则或财务备注为空时返回 null(由调用方回落)。 */
|
||
private function accountMappingRemark(string $platform, string $item, array $businessRemarks, string $goodsName): ?string
|
||
{
|
||
$index = $this->accountMappingIndex();
|
||
$platformKey = $this->normalizeKey($platform);
|
||
|
||
// 先按原样匹配,再按「去掉 wx 前缀」的账单项目匹配(科目映射表里部分项目不带 wx 前缀)
|
||
foreach ($this->itemLookupKeys($item) as $candidate) {
|
||
$rules = $index[$platformKey][$this->normalizeKey($candidate)] ?? [];
|
||
if ($rules === []) {
|
||
continue;
|
||
}
|
||
|
||
if (count($rules) === 1) {
|
||
$remark = trim((string) ($rules[0]['memo'] ?? ''));
|
||
if ($remark !== '') {
|
||
return $remark;
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
// 同一账单项目有多条规则时,用映射表自己的匹配器(按商品名称/业务备注关键词取最具体的)
|
||
try {
|
||
$result = app(AccountMappingMatcher::class)->match($platform, $candidate, $businessRemarks, $goodsName);
|
||
} catch (Throwable) {
|
||
$result = null;
|
||
}
|
||
|
||
$remark = trim((string) ($result['rule']['memo'] ?? ''));
|
||
if ($remark !== '') {
|
||
return $remark;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 账单项目的查找候选:原样优先,其次去掉 wx 前缀。
|
||
*
|
||
* 淘系同一笔费用在不同页签/导出里会出现带 wx 前缀(wx基础软件服务费)与不带前缀(基础软件服务费)两种写法,
|
||
* 按口径应当是同一个总账备注;去掉前缀后再查一次即可命中同一批规则。
|
||
*
|
||
* @return list<string>
|
||
*/
|
||
private function itemLookupKeys(string $item): array
|
||
{
|
||
$item = trim($item);
|
||
$keys = [$item];
|
||
|
||
if ($item !== '' && stripos($item, 'wx') === 0) {
|
||
$withoutPrefix = ltrim(mb_substr($item, 2));
|
||
if ($withoutPrefix !== '') {
|
||
$keys[] = $withoutPrefix;
|
||
}
|
||
}
|
||
|
||
return array_values(array_unique($keys));
|
||
}
|
||
|
||
private function normalizeKey(string $value): string
|
||
{
|
||
return mb_strtolower(preg_replace('/\s+/u', '', $value) ?? $value);
|
||
}
|
||
|
||
/**
|
||
* 给一行账单补上 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,
|
||
$this->mappingItem($row, $tab),
|
||
$this->businessRemarks($row),
|
||
$this->goodsName($row),
|
||
);
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* 取映射用的「账单项目」。
|
||
*
|
||
* 唯品会JX 的科目映射表(财务规则 › 科目映射表)是按页面「账单项目」列维护的
|
||
* (处罚款 / 客服特殊退款-仅退款 / 盘盈盘亏 …,对应 payload.second_remark),
|
||
* 所以这一页签用行内账单项目去查规则,其余页签仍用账单项目列(bill_item)。
|
||
*/
|
||
private function mappingItem(array $row, string $tab): ?string
|
||
{
|
||
if ($tab === 'vipshop-jx') {
|
||
$payload = is_array($row['payload'] ?? null) ? $row['payload'] : [];
|
||
$item = trim((string) ($payload['second_remark'] ?? ''));
|
||
if ($item !== '') {
|
||
return $item;
|
||
}
|
||
}
|
||
|
||
return $row['bill_item'] ?? null;
|
||
}
|
||
|
||
/**
|
||
* 账单上的业务备注(用于科目映射表的「业务备注关键词」条件)。
|
||
*
|
||
* @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,
|
||
// 各平台账单表的业务字段(得物:channel=退货退款订单 / business_type / order_type)
|
||
$row['channel'] ?? null,
|
||
$row['business_type'] ?? null,
|
||
$row['order_type'] ?? 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,
|
||
$payload['channel'] ?? null,
|
||
$payload['business_type'] ?? null,
|
||
$payload['order_type'] ?? 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>>
|
||
*/
|
||
public function decorateAll(iterable $rows, string $tab): \Generator
|
||
{
|
||
foreach ($rows as $row) {
|
||
yield is_array($row) ? $this->decorate($row, $tab) : $row;
|
||
}
|
||
}
|
||
}
|