Add the period balance reconciliation page, permissions, financial closing balances, Jushuitan snapshots for Taobao Alipay, Pinduoduo and Douyin, and explicit unavailable states.
127 lines
5.4 KiB
PHP
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',
|
|
];
|
|
}
|
|
}
|