CaiWuXiTong/tests/Feature/Business/AccountMappingMatcherTest.php

204 lines
8.0 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 Tests\Feature\Business;
use Illuminate\Support\Facades\File;
use Modules\EcommerceFinance\Services\AccountLedgerRemarkMap;
use Modules\EcommerceFinance\Services\AccountMappingMatcher;
use Modules\EcommerceFinance\Services\AccountMappingStore;
use Modules\EcommerceFinance\Services\AccountMappingValidator;
use Tests\Feature\FeatureTestCase;
/**
* 科目映射表的「区分条件」匹配:业务备注 / 商品名称关键词。
*
* 数据同样落在 storage_path('app/account-mapping'),所以每个用例都把 storage 指到临时目录。
*/
class AccountMappingMatcherTest extends FeatureTestCase
{
private string $storageRoot = '';
private AccountMappingStore $store;
private AccountMappingMatcher $matcher;
protected function setUp(): void
{
parent::setUp();
$this->storageRoot = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'account-mapping-matcher-' . uniqid();
File::makeDirectory($this->storageRoot . '/app', 0777, true, true);
$this->app->useStoragePath($this->storageRoot);
$this->store = app(AccountMappingStore::class);
$this->matcher = app(AccountMappingMatcher::class);
}
protected function tearDown(): void
{
if ($this->storageRoot !== '' && File::isDirectory($this->storageRoot)) {
File::deleteDirectory($this->storageRoot);
}
parent::tearDown();
}
/** @param array<string,string> $values */
private function rule(array $values): array
{
return $this->store->create($values + [
'platform' => '测试平台',
'item' => '测试项目',
'category' => '费用',
'dAcct' => '销售费用',
'dSub' => '运费',
'cAcct' => '其他货币资金',
'cSub' => '',
'basis' => '支出基准',
]);
}
/** 同项目两条规则:带业务备注的优先于不带条件的兜底规则 */
public function test_specific_rule_wins_over_fallback(): void
{
$fallback = $this->rule(['item' => '在线支付']);
$erp = $this->rule(['item' => '在线支付', 'memo' => 'ERP软件费', 'dAcct' => '管理费用', 'dSub' => '软件服务费']);
$hit = $this->matcher->match('测试平台', '在线支付', ['ERP软件费'], '聚水潭ERP-专业版');
$this->assertNotNull($hit);
$this->assertTrue($hit['matched']);
$this->assertSame($erp['id'], $hit['rule']['id']);
$miss = $this->matcher->match('测试平台', '在线支付', ['推广充值'], 'P4P充值包');
$this->assertNotNull($miss);
$this->assertFalse($miss['matched']);
$this->assertSame($fallback['id'], $miss['rule']['id']);
$this->assertStringContainsString('兜底', $miss['reason']);
}
/** 商品名称关键词比业务备注更具体:两个都命中时取商品名称那条 */
public function test_goods_keyword_is_more_specific_than_remark(): void
{
$byMemo = $this->rule(['item' => '在线支付', 'memo' => '推广充值']);
$byGoods = $this->rule(['item' => '在线支付', 'memo' => '推广充值', 'goods' => 'P4P充值包']);
$hit = $this->matcher->match('测试平台', '在线支付', ['推广充值'], 'P4P充值包-2026年7月');
$this->assertNotNull($hit);
$this->assertSame($byGoods['id'], $hit['rule']['id']);
$other = $this->matcher->match('测试平台', '在线支付', ['推广充值'], '万相台充值');
$this->assertNotNull($other);
$this->assertSame($byMemo['id'], $other['rule']['id']);
}
/** 月份式备注(2607退款 / 202607订单)不参与匹配,否则规则会随月份膨胀 */
public function test_period_labels_do_not_match_business_remark(): void
{
$rule = $this->rule(['item' => '退款', 'memo' => '退款']);
$this->assertTrue($this->matcher->isPeriodLabel('2607退款'));
$this->assertTrue($this->matcher->isPeriodLabel('202607订单'));
$period = $this->matcher->match('测试平台', '退款', ['2607退款']);
$this->assertNotNull($period);
$this->assertFalse($period['matched']);
$business = $this->matcher->match('测试平台', '退款', ['退款']);
$this->assertNotNull($business);
$this->assertTrue($business['matched']);
$this->assertSame($rule['id'], $business['rule']['id']);
}
/** 商品名称关键词支持空格分隔的多个词,全部命中才算 */
public function test_goods_keyword_supports_multiple_terms(): void
{
$rule = $this->rule(['item' => '在线支付', 'goods' => '聚水潭 ERP']);
$hit = $this->matcher->match('测试平台', '在线支付', [], '聚水潭ERP-专业版-订单付款');
$this->assertNotNull($hit);
$this->assertTrue($hit['matched']);
$this->assertSame($rule['id'], $hit['rule']['id']);
$miss = $this->matcher->match('测试平台', '在线支付', [], '聚水潭-专业版');
$this->assertNotNull($miss);
$this->assertFalse($miss['matched']);
}
public function test_performance_fee_item_uses_the_matched_rule_fee_memo(): void
{
$rule = $this->rule([
'platform' => '京东',
'item' => '平台服务费',
'memo' => '平台服务费',
'feeMemo' => '平台服务费',
'category' => '费用',
]);
$matched = app(AccountLedgerRemarkMap::class)->accountMappingMatch(
'京东-测试店铺',
'jd-transactions',
'平台服务费',
);
$this->assertNotNull($matched);
$this->assertTrue($matched['matched']);
$this->assertSame($rule['id'], $matched['rule']['id']);
$this->assertSame('平台服务费', $matched['rule']['feeMemo']);
}
/** 校验:同项目、条件不同 = 正常;条件相同 = 才算重复 */
public function test_validator_flags_only_same_condition_pairs(): void
{
$first = $this->rule(['item' => '交易分账_货款', 'memo' => '订单收入']);
$second = $this->rule(['item' => '交易分账_货款', 'memo' => '退款']);
$this->assertNotContains('duplicate_pair', $this->issueCodes([$first['id'], $second['id']]), '条件不同的两条不该报重复');
$third = $this->rule(['item' => '交易分账_货款', 'memo' => '退款']);
$this->assertContains('duplicate_pair', $this->issueCodes([$second['id'], $third['id']]), '条件相同的两条要报重复');
}
/** 「-」表示不限:存进去按空处理,匹配时不会拿它当关键词去找 */
public function test_dash_means_no_condition(): void
{
$rule = $this->rule(['item' => '在线支付', 'memo' => '-', 'goods' => '-']);
$this->assertSame('', $rule['memo']);
$this->assertSame('', $rule['goods']);
$hit = $this->matcher->match('测试平台', '在线支付', ['推广充值'], 'P4P充值包');
$this->assertNotNull($hit);
$this->assertFalse($hit['matched']);
$this->assertSame($rule['id'], $hit['rule']['id']);
}
/** 校验:同项目下有兜底规则时给出提示,提醒它只做兜底 */
public function test_validator_hints_fallback_rule(): void
{
$fallback = $this->rule(['item' => '在线支付']);
$specific = $this->rule(['item' => '在线支付', 'memo' => 'ERP软件费']);
$codes = $this->issueCodes([$fallback['id']]);
$this->assertContains('fallback_rule', $codes);
$this->assertNotContains('duplicate_pair', $codes);
$this->assertNotContains('duplicate_pair', $this->issueCodes([$specific['id']]));
}
/** @param array<int,int> $ids @return array<int,string> */
private function issueCodes(array $ids): array
{
$result = app(AccountMappingValidator::class)->validate($this->store->all());
$codes = [];
foreach ($ids as $id) {
foreach ($result['issues'][$id] ?? [] as $issue) {
$codes[] = $issue['code'];
}
}
return $codes;
}
}