CaiWuXiTong/tests/Unit/EcommerceFinance/PeriodBalanceReconciliationServiceTest.php
qiuxiang 739fff215a feat: period balance reconciliation
Add the period balance reconciliation page, permissions, financial closing balances, Jushuitan snapshots for Taobao Alipay, Pinduoduo and Douyin, and explicit unavailable states.
2026-11-11 19:33:39 +08:00

127 lines
5.4 KiB
PHP

<?php
namespace Tests\Unit\EcommerceFinance;
use Mockery;
use Modules\EcommerceFinance\Repositories\JushuitanShopBalanceRepository;
use Modules\EcommerceFinance\Services\PeriodBalanceReconciliationService;
use Modules\EcommerceFinance\Services\StoreReceivingBalanceOverviewService;
use Tests\TestCase;
class PeriodBalanceReconciliationServiceTest extends TestCase
{
public function test_it_merges_financial_and_jushuitan_balances_and_marks_unconnected_platforms(): void
{
$result = $this->service(
snapshotRows: [
$this->snapshot('taobao', '淘系店铺', 9950),
$this->snapshot('pinduoduo', '拼多多店铺', 9000),
$this->snapshot('pinduoduo', '仅聚水潭店铺', 800),
],
snapshotDates: [
'taobao' => '2026-10-31',
'pinduoduo' => '2026-10-31',
'douyin' => '2026-10-31',
],
)->build('2026-10-01', '2026-10-31');
$rows = collect($result['stores'])->keyBy('key');
$this->assertSame('normal', $rows['taobao-alipay|淘系店铺']['status']);
$this->assertSame(50.0, $rows['taobao-alipay|淘系店铺']['difference']);
$this->assertSame('abnormal', $rows['pinduoduo|拼多多店铺']['status']);
$this->assertSame(1000.0, $rows['pinduoduo|拼多多店铺']['difference']);
$this->assertSame('jst_missing', $rows['douyin|抖音店铺']['status']);
$this->assertSame('unavailable', $rows['taobao-wechat|微信店铺']['status']);
$this->assertSame('financial_missing', $rows['pinduoduo|仅聚水潭店铺']['status']);
$this->assertSame(8, count($result['sources']));
$this->assertSame(5, $result['summary']['unconnected_platform_count']);
$this->assertSame(3, count($result['dual_source']['connected_platforms']));
$this->assertSame(200.0, $result['tolerance_yuan']);
}
public function test_it_marks_a_lagging_snapshot_as_stale_instead_of_matching_old_balances(): void
{
$result = $this->service(
snapshotRows: [$this->snapshot('taobao', '淘系店铺', 9950)],
snapshotDates: [
'taobao' => '2026-10-25',
'pinduoduo' => '2026-10-24',
'douyin' => '2026-10-23',
],
)->build('2026-11-01', '2026-11-30');
$taobao = collect($result['stores'])
->firstWhere('platform_slug', 'taobao-alipay');
$this->assertSame('stale', $taobao['status']);
$this->assertSame('2026-10-25', $taobao['snapshot_date']);
$this->assertSame('取数时间点差', $taobao['reasons'][0]['kind']);
$this->assertSame(1, $result['summary']['stale_count']);
$this->assertSame('stale', collect($result['sources'])->firstWhere('slug', 'taobao-alipay')['status']);
}
private function service(array $snapshotRows, array $snapshotDates): PeriodBalanceReconciliationService
{
$financialRows = [
$this->financial('taobao-alipay', '淘系支付宝账户', '淘系店铺', 10000),
$this->financial('pinduoduo', '拼多多账户', '拼多多店铺', 10000),
$this->financial('douyin', '抖音账户', '抖音店铺', 3000),
$this->financial('taobao-wechat', '淘系微信账户', '微信店铺', 2000),
];
$overview = Mockery::mock(StoreReceivingBalanceOverviewService::class);
$overview->shouldReceive('overview')
->once()
->andReturn([
'summary' => [
'opening_balance' => 20000,
'income' => 5000,
'expense' => 4000,
'balance' => 21000,
'platform_count' => 8,
'available_platform_count' => 8,
'complete' => true,
],
'platforms' => [],
]);
$overview->shouldReceive('storeBalancesForPlatforms')
->andReturn(['rows' => $financialRows, 'errors' => []]);
$repository = Mockery::mock(JushuitanShopBalanceRepository::class);
$repository->shouldReceive('latestComparisonSnapshots')
->andReturn(['rows' => $snapshotRows, 'dates' => $snapshotDates]);
$repository->shouldReceive('comparisonSnapshotsBetween')
->once()
->andReturn($snapshotRows);
$repository->shouldReceive('earliestBalanceDate')->once()->andReturn('2026-09-01');
$repository->shouldReceive('latestBalanceDate')->once()->andReturn('2026-10-31');
return new PeriodBalanceReconciliationService($overview, $repository);
}
private function financial(string $platformSlug, string $platform, string $storeName, float $amount): array
{
return [
'platform_slug' => $platformSlug,
'platform' => $platform,
'store_name' => $storeName,
'opening_balance' => $amount,
'income' => 0.0,
'expense' => 0.0,
'amount' => $amount,
'available' => true,
];
}
private function snapshot(string $scope, string $storeName, float $balance): object
{
return (object) [
'source_scope' => $scope,
'normalized_store_name' => JushuitanShopBalanceRepository::normalizeStoreName($storeName),
'store_name' => $storeName,
'balance' => $balance,
'balance_date' => '2026-10-31',
];
}
}