203 lines
9.2 KiB
PHP
203 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use App\Business\UpstreamBusinessInformation;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Mockery;
|
|
use Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\InstallationDetailMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\StoreReportRepository;
|
|
use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class StoreReportRepositoryTest extends TestCase
|
|
{
|
|
private StoreReportRepository $repository;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config([
|
|
'database.connections.business_mirror' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
|
|
'upstream_mirrors.connection' => 'business_mirror',
|
|
]);
|
|
DB::purge('business_mirror');
|
|
$this->createTables();
|
|
DB::connection('business_mirror')->table('store_report_import_states')->insert([
|
|
['dataset_key' => StoreReportRepository::BILLS_DATASET, 'status' => 'succeeded', 'active_generation' => 'bills-v1'],
|
|
['dataset_key' => StoreReportRepository::LIFECYCLES_DATASET, 'status' => 'succeeded', 'active_generation' => 'lifecycles-v1'],
|
|
]);
|
|
$this->repository = new StoreReportRepository(app('db'));
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Carbon::setTestNow();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_lifecycle_boundaries_split_monthly_income_without_double_counting(): void
|
|
{
|
|
$db = DB::connection('business_mirror');
|
|
$db->table('store_report_lifecycle_rows')->insert([
|
|
$this->lifecycle(1, '店铺A', '主体甲', '2026-05-01', '2026-07-01'),
|
|
$this->lifecycle(2, '店铺A', '主体乙', '2026-07-01', null),
|
|
]);
|
|
$db->table('store_report_bill_rows')->insert([
|
|
$this->bill(1, '店铺A', '2026-04-30 23:59:59', 9),
|
|
$this->bill(2, '店铺A', '2026-05-01 00:00:00', 100),
|
|
$this->bill(3, '店铺A', '2026-06-30 23:59:59', 60),
|
|
$this->bill(4, '店铺A', '2026-07-01 00:00:00', 70),
|
|
$this->bill(5, '店铺A', '2026-08-01 00:00:00', 80),
|
|
$this->bill(6, '店铺A', '2027-01-01 00:00:00', 5),
|
|
]);
|
|
|
|
$rows = $this->repository->rows([
|
|
['erp_store_name' => '店铺A', 'store_id' => 'STORE-001', 'company_name' => '旧主体', 'store_status' => '3'],
|
|
], 2026);
|
|
|
|
$this->assertSame(['主体甲', '主体乙'], array_column($rows, 'entity'));
|
|
$this->assertSame('STORE-001', $rows[0]['store_id']);
|
|
$this->assertSame([160.0, 155.0], array_column($rows, 'period_sales'));
|
|
$this->assertSame([100.0, 0.0], array_column($rows, 'sales_05'));
|
|
$this->assertSame([0.0, 70.0], array_column($rows, 'sales_07'));
|
|
$this->assertSame(['关店中', '关店中'], array_column($rows, 'status'));
|
|
}
|
|
|
|
public function test_failed_refresh_keeps_the_previous_active_generation_readable(): void
|
|
{
|
|
DB::connection('business_mirror')->table('store_report_import_states')
|
|
->where('dataset_key', StoreReportRepository::BILLS_DATASET)
|
|
->update(['status' => 'failed', 'pending_generation' => null]);
|
|
DB::connection('business_mirror')->table('store_report_bill_rows')->insert(
|
|
$this->bill(1, '店铺A', '2026-05-03 12:00:00', 25.5),
|
|
);
|
|
|
|
$rows = $this->repository->rows([
|
|
['erp_store_name' => '店铺A', 'company_name' => '公司表主体', 'opened_at' => '2026-05-01'],
|
|
], 2026);
|
|
|
|
$this->assertSame(25.5, $rows[0]['period_sales']);
|
|
$this->assertSame(25.5, $rows[0]['sales_05']);
|
|
$this->assertSame('公司表主体', $rows[0]['entity']);
|
|
}
|
|
|
|
public function test_missing_first_import_is_reported_instead_of_returning_misleading_zeroes(): void
|
|
{
|
|
DB::connection('business_mirror')->table('store_report_import_states')
|
|
->where('dataset_key', StoreReportRepository::BILLS_DATASET)->delete();
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessage('中央同步中');
|
|
$this->repository->rows([['erp_store_name' => '店铺A']], 2026);
|
|
}
|
|
|
|
public function test_upstream_business_information_exposes_shared_store_report_rows(): void
|
|
{
|
|
DB::connection('business_mirror')->table('store_report_lifecycle_rows')->insert([
|
|
$this->lifecycle(1, '店铺A', '主体甲', '2026-05-01', null),
|
|
]);
|
|
DB::connection('business_mirror')->table('store_report_bill_rows')->insert([
|
|
$this->bill(1, '店铺A', '2026-06-30 23:59:59', 60),
|
|
]);
|
|
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
|
|
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
|
|
$mirror->shouldReceive('allStores')->once()->andReturn([[
|
|
'erp_store_name' => '店铺A', 'company_name' => '公司表主体', 'store_status' => '3',
|
|
]]);
|
|
$service = new UpstreamBusinessInformation(
|
|
Mockery::mock(UpstreamQueryCacheFactory::class),
|
|
$mirror,
|
|
Mockery::mock(InstallationDetailMirrorRepository::class),
|
|
$this->repository,
|
|
);
|
|
|
|
$paginator = $service->paginate('store-management', 'store-report', 50);
|
|
|
|
$this->assertSame(1, $paginator->total());
|
|
$this->assertSame('主体甲', $paginator->items()[0]->entity);
|
|
$this->assertSame(60.0, $paginator->items()[0]->period_sales);
|
|
}
|
|
|
|
public function test_subject_operating_report_uses_real_recent_and_entity_sales(): void
|
|
{
|
|
Carbon::setTestNow('2026-09-01 10:00:00');
|
|
DB::connection('business_mirror')->table('store_report_lifecycle_rows')->insert([
|
|
$this->lifecycle(1, '店铺A', '主体甲', '2026-01-01', null),
|
|
$this->lifecycle(2, '店铺B', '主体甲', '2026-01-01', null),
|
|
]);
|
|
DB::connection('business_mirror')->table('store_report_bill_rows')->insert([
|
|
$this->bill(1, '店铺A', '2026-06-10 10:00:00', 10),
|
|
$this->bill(2, '店铺A', '2026-07-10 10:00:00', 20),
|
|
$this->bill(3, '店铺A', '2026-08-10 10:00:00', 30),
|
|
$this->bill(4, '店铺B', '2026-08-11 10:00:00', 40),
|
|
]);
|
|
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
|
|
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
|
|
$mirror->shouldReceive('allStores')->once()->andReturn([
|
|
['erp_store_name' => '店铺A', 'company_name' => '主体甲', 'store_status' => '0'],
|
|
['erp_store_name' => '店铺B', 'company_name' => '主体甲', 'store_status' => '0'],
|
|
]);
|
|
$service = new UpstreamBusinessInformation(
|
|
Mockery::mock(UpstreamQueryCacheFactory::class),
|
|
$mirror,
|
|
Mockery::mock(InstallationDetailMirrorRepository::class),
|
|
$this->repository,
|
|
);
|
|
|
|
$rows = $service->paginate('company-management', 'subject-operating-report', 50)->items();
|
|
|
|
$this->assertCount(2, $rows);
|
|
$this->assertSame(10.0, $rows[0]->recent_three_month_sales_1);
|
|
$this->assertSame(20.0, $rows[0]->recent_three_month_sales_2);
|
|
$this->assertSame(30.0, $rows[0]->recent_three_month_sales_3);
|
|
$this->assertSame(60.0, $rows[0]->store_sales_12m);
|
|
$this->assertSame(100.0, $rows[0]->entity_sales_12m);
|
|
$this->assertSame(100.0, $rows[1]->entity_sales_12m);
|
|
}
|
|
|
|
private function createTables(): void
|
|
{
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('store_report_import_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status');
|
|
$table->string('active_generation')->nullable();
|
|
$table->string('pending_generation')->nullable();
|
|
});
|
|
$schema->create('store_report_bill_rows', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('generation');
|
|
$table->string('store_name');
|
|
$table->dateTime('occurred_at')->nullable();
|
|
$table->decimal('income', 20, 4)->default(0);
|
|
});
|
|
$schema->create('store_report_lifecycle_rows', function (Blueprint $table): void {
|
|
$table->increments('id');
|
|
$table->string('generation');
|
|
$table->string('erp_store_name');
|
|
$table->string('company_name')->nullable();
|
|
$table->dateTime('effective_at')->nullable();
|
|
$table->dateTime('expires_at')->nullable();
|
|
});
|
|
}
|
|
|
|
private function bill(int $id, string $store, string $time, float $income): array
|
|
{
|
|
return ['id' => $id, 'generation' => 'bills-v1', 'store_name' => $store, 'occurred_at' => $time, 'income' => $income];
|
|
}
|
|
|
|
private function lifecycle(int $id, string $store, string $company, ?string $from, ?string $to): array
|
|
{
|
|
return [
|
|
'id' => $id, 'generation' => 'lifecycles-v1', 'erp_store_name' => $store,
|
|
'company_name' => $company, 'effective_at' => $from, 'expires_at' => $to,
|
|
];
|
|
}
|
|
}
|