[ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], 'upstream_mirrors.connection' => 'business_mirror', 'upstream_mirrors.datasets' => [ 'operations_replica.rpt.fys_companyInfo' => $this->definition('fys_companyInfo', 'ops_fys_company_info'), 'operations_replica.rpt.fys_shopInfo' => $this->definition('fys_shopInfo', 'ops_fys_shop_info'), 'operations_replica.rpt.fys_TrademarkInfo' => $this->definition('fys_TrademarkInfo', 'ops_fys_trademark_info'), ], ]); DB::purge('business_mirror'); $this->createTables(); $mirror = new GenericBusinessMirrorRepository(app('db')); $this->repository = new BusinessInformationMirrorRepository( new BusinessMirrorDatasetRegistry(), $mirror, ); } public function test_it_reads_all_three_ready_mirrors_with_pagination_and_business_filters(): void { $connection = DB::connection('business_mirror'); $connection->table('ops_fys_company_info')->insert([ $this->company(1, '统一代码', '旧公司'), $this->company(2, '统一代码', '新公司'), $this->company(3, '其他代码', '其他公司'), ]); $connection->table('fin_companies')->insert([ ['name' => '旧公司', 'tax_type' => '小规模纳税人', 'business_type' => '/', 'status' => '保留'], ['name' => '新公司', 'tax_type' => '小规模纳税人', 'business_type' => '个人独资企业', 'status' => '保留'], ['name' => '其他公司', 'tax_type' => '一般纳税人', 'business_type' => '核定', 'status' => '待注销'], ]); $connection->table('ops_fys_shop_info')->insert([ ['Id' => 8, 'openTime' => '2026-08-14 10:00:00', 'shopName' => '上游店铺名', 'erpShopName' => '测试店', 'unifiedSocialCreditCode' => '统一代码', 'trademarkNumber' => 'TM', 'platform' => '天猫', 'responsibleDepartment' => '电商', 'shopHead' => '负责人', 'shopState' => '0', 'shopType' => '直营', 'channelType' => '线上'], ]); $connection->table('ops_fys_trademark_info')->insert([ ['Id' => 5, 'trademarkNumber' => 'TM', 'trademarkName' => '有效商标', 'internationalClassification' => '20', 'trademarkStatus' => '1', 'registrationDate' => '2020-01-01', 'termOfValidity' => '2030-01-01', 'remark' => null, 'isDelete' => 0], ['Id' => 6, 'trademarkNumber' => 'TM', 'trademarkName' => '已删除', 'internationalClassification' => '20', 'trademarkStatus' => '0', 'registrationDate' => null, 'termOfValidity' => null, 'remark' => null, 'isDelete' => 1], ]); $this->markReady(); $connection->table('ops_fys_company_info')->where('Id', 1)->update(['registeredCapitalVaildTime' => '2027-01-01']); $connection->table('ops_fys_company_info')->where('Id', 2)->update([ 'businessLicenseType' => '店铺-普通公司', 'taxCategoryAndType' => '小规模纳税人', 'legalRepresentative' => '张三', 'registeredAddress' => '上海市浦东新区测试路1号', ]); $connection->table('ops_fys_company_info')->where('Id', 3)->update([ 'companyStatus' => '1', 'taxCategoryAndType' => '一般纳税人', ]); $connection->table('ops_fys_shop_info')->where('Id', 8)->update(['sotreId' => 'STORE-8', 'renewalTime' => '2027-08-14', 'shopBankAccount' => 'fixture-account', 'trademarkNumber' => 'TM-5']); $connection->table('ops_fys_trademark_info')->where('Id', 5)->update(['trademarkNumber' => 'TM-5', 'approvedCommodities' => '家具', 'transferenceUser' => '测试持有人', 'registrationAddress' => '测试地址', 'custodyDepartment' => '资料部']); $companies = $this->repository->paginate('company-information.read', 2, 2); $store = $this->repository->paginate('store-information.read', 1, 20); $trademarks = $this->repository->paginate('trademark-information.read', 1, 20); $receivingStores = $this->repository->receivingAccountStores(); $filteredStores = $this->repository->allStores([ 'store_name' => '上游店铺名', 'store_id' => 'STORE-8', ]); $normalStores = $this->repository->allStores(['store_status' => '正常']); $closedStores = $this->repository->allStores(['store_status' => '已关闭']); $legacyStatusStores = $this->repository->allStores(['status' => '0']); $filteredStore = $this->repository->paginate('store-information.read', 1, 20, [ 'platform' => '天猫', 'store_status' => '0', 'opened_at_from' => '2026-08-14', ]); $filteredTrademark = $this->repository->paginate('trademark-information.read', 1, 20, [ 'trademark_status' => '1', 'registration_date_from' => '2020-01-01', ]); $filteredCompany = $this->repository->paginate('company-information.read', 1, 20, ['company_name' => '新公司']); $relatedCompany = $this->repository->paginate('company-information.read', 1, 20, ['related_people' => '张']); $typedCompany = $this->repository->paginate('company-information.read', 1, 20, [ 'taxpayer_type' => '小规模纳税人', 'company_status' => '正常', 'company_type' => '店铺-普通公司', ]); $businessTypedCompany = $this->repository->paginate('company-information.read', 1, 20, [ 'business_registration_type' => '个人独资企业', ]); $cityCompany = $this->repository->paginate('company-information.read', 1, 20, ['city' => '上海']); $companyClassifications = $this->repository->companyClassificationsByNames(['旧公司', '新公司', '其他公司']); $companyDetails = $this->repository->companiesByNames(['新公司']); $this->assertSame(3, $companies['total']); $this->assertSame(1, $companies['data'][0]['id']); $this->assertSame('新公司', $store['data'][0]['company_name']); $this->assertSame('有效商标', $store['data'][0]['trademark_name']); $this->assertSame(1, $trademarks['total']); $this->assertSame('有效商标', $trademarks['data'][0]['trademark_name']); $this->assertSame('2027-01-01', $companies['data'][0]['subscribed_at']); $this->assertSame('STORE-8', $store['data'][0]['store_id']); $this->assertSame('上游店铺名', $store['data'][0]['store_name']); $this->assertCount(1, $filteredStores); $this->assertCount(1, $normalStores); $this->assertSame([], $closedStores); $this->assertCount(1, $legacyStatusStores); $this->assertSame([], $this->repository->allStores(['store_id' => '测试店'])); $this->assertSame('2027-08-14', $store['data'][0]['renewed_at']); $this->assertSame('负责人', $store['data'][0]['store_manager']); $this->assertSame('上游店铺名', $receivingStores[0]['shop_name']); $this->assertSame('天猫', $receivingStores[0]['source_platform']); $this->assertSame('新公司', $receivingStores[0]['company_name']); $this->assertSame('fixture-account', $store['data'][0]['withdrawal_account']); $this->assertSame('TM-5', $trademarks['data'][0]['trademark_number']); $this->assertSame('家具', $trademarks['data'][0]['approved_goods']); $this->assertSame('测试持有人', $trademarks['data'][0]['final_holder']); $this->assertSame(1, $filteredStore['total']); $this->assertSame(1, $filteredTrademark['total']); $this->assertSame('新公司', $filteredCompany['data'][0]['company_name']); $this->assertSame('新公司', $relatedCompany['data'][0]['company_name']); $this->assertSame(1, $typedCompany['total']); $this->assertSame(1, $businessTypedCompany['total']); $this->assertSame('个人独资企业', $businessTypedCompany['data'][0]['business_registration_type']); $this->assertSame(1, $cityCompany['total']); $this->assertSame('保留', $companyClassifications['新公司']['status']); $this->assertSame('待注销', $companyClassifications['其他公司']['status']); $this->assertSame('小规模纳税人', $companyClassifications['新公司']['tax_type']); $this->assertSame('个人独资企业', $companyClassifications['新公司']['business_registration_type']); $this->assertSame('/', $companyClassifications['旧公司']['business_registration_type']); $this->assertSame('个人独资企业', $companyDetails['新公司'][0]['business_registration_type']); $this->assertSame([ 'total' => 3, 'normal' => 2, 'deregistered' => 1, 'general_taxpayer' => 1, 'small_taxpayer' => 2, ], $companies['summary']); $this->assertSame([ 'total' => 1, 'normal' => 1, 'closing' => 0, 'closed' => 0, 'platforms' => 1, ], $store['summary']); $this->assertSame([ 'total' => 1, 'enabled' => 1, 'disabled' => 0, 'holders' => 1, 'departments' => 1, ], $trademarks['summary']); $this->assertSame(1, $filteredCompany['summary']['total']); $this->assertSame(1, $filteredTrademark['summary']['enabled']); } public function test_dataset_is_not_ready_until_initial_full_sync_is_marked_complete(): void { $this->assertFalse($this->repository->ready('company-information.read')); $this->markReady(['operations_replica.rpt.fys_companyInfo']); $this->assertTrue($this->repository->ready('company-information.read')); } private function createTables(): void { $schema = Schema::connection('business_mirror'); $schema->create('business_mirror_table_states', function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->dateTime('initial_completed_at')->nullable(); }); $schema->create('ops_fys_company_info', function (Blueprint $table): void { $table->integer('Id'); $table->string('unifiedSocialCreditCode')->nullable(); $table->string('businessLicenseName')->nullable(); $table->string('registeredCapital')->nullable(); $table->string('businessLicenseType')->nullable(); $table->dateTime('IncorporationDate')->nullable(); $table->string('legalRepresentative')->nullable(); $table->string('registeredAddress')->nullable(); $table->text('businessScope')->nullable(); $table->string('accountSerialNumber')->nullable(); $table->string('companyStatus')->nullable(); $table->string('financialManager')->nullable(); $table->string('supervisor')->nullable(); $table->string('taxCategoryAndType')->nullable(); $table->dateTime('registeredCapitalVaildTime')->nullable(); }); $schema->create('ops_fys_shop_info', function (Blueprint $table): void { $table->integer('Id'); $table->dateTime('openTime')->nullable(); $table->string('shopName')->nullable(); $table->string('erpShopName')->nullable(); $table->string('unifiedSocialCreditCode')->nullable(); $table->string('trademarkNumber')->nullable(); $table->string('platform')->nullable(); $table->string('responsibleDepartment')->nullable(); $table->string('shopHead')->nullable(); $table->string('customerDeptManager')->nullable(); $table->string('shopState')->nullable(); $table->string('shopType')->nullable(); $table->string('channelType')->nullable(); $table->string('sotreId')->nullable(); $table->dateTime('renewalTime')->nullable(); $table->string('shopBankAccount')->nullable(); }); $schema->create('fin_companies', function (Blueprint $table): void { $table->increments('id'); $table->string('name')->unique(); $table->string('tax_type')->nullable(); $table->string('business_type')->nullable(); $table->string('status')->nullable(); }); $schema->create('ops_fys_trademark_info', function (Blueprint $table): void { $table->integer('Id'); $table->string('trademarkNumber')->nullable(); $table->string('trademarkName')->nullable(); $table->string('internationalClassification')->nullable(); $table->string('trademarkStatus')->nullable(); $table->dateTime('registrationDate')->nullable(); $table->dateTime('termOfValidity')->nullable(); $table->text('remark')->nullable(); $table->integer('isDelete')->default(0); foreach (['approvedCommodities', 'transferenceUser', 'registrationAddress', 'custodyDepartment'] as $column) $table->string($column)->nullable(); }); } private function markReady(?array $keys = null): void { $keys ??= array_keys((array) config('upstream_mirrors.datasets')); foreach ($keys as $key) { DB::connection('business_mirror')->table('business_mirror_table_states')->insert([ 'dataset_key' => $key, 'initial_completed_at' => '2026-08-14 12:00:00', ]); } } private function definition(string $object, string $target): array { return ['source_key' => 'operations_replica', 'schema' => 'rpt', 'object' => $object, 'target' => $target, 'keys' => ['Id']]; } private function company(int $id, string $code, string $name): array { return ['Id' => $id, 'unifiedSocialCreditCode' => $code, 'businessLicenseName' => $name, 'registeredCapital' => null, 'businessLicenseType' => null, 'IncorporationDate' => null, 'legalRepresentative' => null, 'registeredAddress' => null, 'businessScope' => null, 'accountSerialNumber' => null, 'companyStatus' => '0', 'financialManager' => null, 'supervisor' => null, 'taxCategoryAndType' => null]; } }