Restore the existing financial overview totals while keeping lazy trend loading.
159 lines
6.8 KiB
PHP
159 lines
6.8 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']);
|
|
}
|
|
|
|
public function test_it_limits_trend_queries_to_five_representative_dates(): void
|
|
{
|
|
$financialRows = [
|
|
$this->financial('taobao-alipay', '淘系支付宝账户', '淘系店铺', 10000),
|
|
$this->financial('pinduoduo', '拼多多账户', '拼多多店铺', 10000),
|
|
$this->financial('douyin', '抖音账户', '抖音店铺', 3000),
|
|
];
|
|
$snapshotRows = [];
|
|
for ($day = 7; $day <= 26; $day++) {
|
|
$snapshotRows[] = (object) [
|
|
'source_scope' => 'taobao',
|
|
'normalized_store_name' => '淘系店铺',
|
|
'store_name' => '淘系店铺',
|
|
'balance' => 9950,
|
|
'balance_date' => '2026-10-' . str_pad((string) $day, 2, '0', STR_PAD_LEFT),
|
|
];
|
|
}
|
|
|
|
$overview = Mockery::mock(StoreReceivingBalanceOverviewService::class);
|
|
$overview->shouldReceive('storeBalancesForPlatforms')
|
|
->times(5)
|
|
->andReturn(['rows' => $financialRows, 'errors' => []]);
|
|
$repository = Mockery::mock(JushuitanShopBalanceRepository::class);
|
|
$repository->shouldReceive('comparisonSnapshotsBetween')
|
|
->once()
|
|
->andReturn($snapshotRows);
|
|
|
|
$result = (new PeriodBalanceReconciliationService($overview, $repository))
|
|
->buildTrend('2026-10-01', '2026-10-31');
|
|
|
|
$this->assertCount(5, $result['points']);
|
|
$this->assertSame('2026-10-07', $result['points'][0]['date']);
|
|
$this->assertSame('2026-10-31', $result['points'][4]['date']);
|
|
}
|
|
|
|
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('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',
|
|
];
|
|
}
|
|
}
|