['driver'=>'sqlite','database'=>':memory:','prefix'=>''], 'upstream_mirrors.connection'=>'business_mirror']); DB::purge('business_mirror'); $definitions=[]; foreach ([InstallationDetailMirrorRepository::DATASET=>'fixture_install',InstallationDetailMirrorRepository::ITEM_DATASET=>'fixture_items'] as $key=>$table) { $definitions[$key]=['source_key'=>'jiansun','schema'=>'dbo','object'=>explode('.',$key)[2],'target'=>$table,'keys'=>['Id']]; } $definitions[InstallationDetailMirrorRepository::DATASET]['minimum_datetime'] = [ 'column' => 'ShipTIme', 'value' => '2026-01-01 00:00:00', ]; config(['upstream_mirrors.datasets'=>$definitions]); $schema=Schema::connection('business_mirror'); $schema->create('business_mirror_table_states',function(Blueprint $t){$t->string('dataset_key')->primary();$t->dateTime('initial_completed_at')->nullable();}); $schema->create('fixture_install',function(Blueprint $t){ $t->integer('Id'); foreach(['SourceGuid','OrderNum','ordernum_id','shop_name','shopcode','SubmitTime','ShipTIme','kfshTime','cwshTime','apistatus','kfsh','cwsh','BidStatus','costtaker','costWay','installComplete','Delete','installCompanyName','TypeDto'] as $c) $t->string($c)->nullable(); $t->integer('StatusId')->nullable()->default(0); foreach(['installcost','settleAmount','acceptAmount'] as $c) $t->decimal($c,14,2)->default(0); }); $schema->create('fixture_items',function(Blueprint $t){ $t->integer('Id'); $t->string('guid'); $t->string('shopproid'); $t->string('Sourceno')->nullable(); $t->dateTime('kfshTime')->nullable(); $t->decimal('settleItemAmount',14,2)->default(0); $t->decimal('acceptItemAmount',14,2)->default(0); }); foreach(array_keys($definitions) as $key) DB::connection('business_mirror')->table('business_mirror_table_states')->insert(['dataset_key'=>$key,'initial_completed_at'=>'2026-08-01']); DB::connection('business_mirror')->table('fixture_install')->insert([ ['Id'=>1,'SourceGuid'=>'A','OrderNum'=>'OLD','shop_name'=>'旧店','SubmitTime'=>'2026-08-01','ShipTIme'=>'2026-08-02','installcost'=>500], ['Id'=>2,'SourceGuid'=>'A','OrderNum'=>'NEW','shop_name'=>'测试店','shopcode'=>'SHOP-A','SubmitTime'=>'2026-08-20 23:59:59','ShipTIme'=>'2026-08-21','installcost'=>20], ['Id'=>3,'SourceGuid'=>'B','OrderNum'=>'OTHER','shop_name'=>'其他店','SubmitTime'=>'2026-08-20','ShipTIme'=>'2026-08-21','installcost'=>30], ['Id'=>4,'SourceGuid'=>'C','OrderNum'=>'NEXT','shop_name'=>'测试店','SubmitTime'=>'2026-08-21','ShipTIme'=>'2026-08-22','installcost'=>40], ['Id'=>5,'SourceGuid'=>'D','OrderNum'=>'PRE-2026','shop_name'=>'测试店','SubmitTime'=>'2026-08-21','ShipTIme'=>'2025-12-31 23:59:59','installcost'=>999], ['Id'=>6,'SourceGuid'=>'E','OrderNum'=>'NULL-SHIP','shop_name'=>'测试店','SubmitTime'=>'2026-08-21','ShipTIme'=>null,'installcost'=>999], ]); DB::connection('business_mirror')->table('fixture_items')->insert([ ['Id'=>1,'guid'=>'A','shopproid'=>'PRODUCT-A'],['Id'=>2,'guid'=>'A','shopproid'=>'PRODUCT-A'], ]); $this->repository=new InstallationDetailMirrorRepository(new GenericBusinessMirrorRepository(app('db')),new BusinessMirrorDatasetRegistry()); } public function test_filters_apply_to_deduplicated_list_count_and_summary(): void { $filters=['shop_name'=>'测试店']; $page=$this->repository->paginate(2,1,$filters); $this->assertSame(2,$page['total']); $this->assertSame(2,$page['data'][0]['id']); $this->assertSame(60.0,$this->repository->summary($filters)['requested']); $filters['created_to']='2026-08-20'; $this->assertSame(1,$this->repository->paginate(1,50,$filters)['total']); $this->assertSame(20.0,$this->repository->summary($filters)['requested']); $this->assertSame(0,$this->repository->paginate(1,50,['shop_name'=>"' OR 1=1 --"])['total']); $this->assertSame(1,$this->repository->paginate(1,50,['shop_code'=>'SHOP-A'])['total']); } public function test_product_filter_does_not_multiply_main_rows(): void { $filters=['product_id'=>'PRODUCT-A']; $this->assertSame(1,$this->repository->paginate(1,50,$filters)['total']); $this->assertSame(20.0,$this->repository->summary($filters)['requested']); $this->assertSame('NEW',$this->repository->details('A')['data'][0]['transaction_no']); $this->assertSame([],$this->repository->details('missing')['data']); } public function test_shipment_cutoff_hides_old_and_null_records_from_list_summary_and_details(): void { DB::connection('business_mirror')->table('fixture_items')->insert([ ['Id'=>30,'guid'=>'D','shopproid'=>'OLD-PRODUCT'], ['Id'=>31,'guid'=>'E','shopproid'=>'NULL-PRODUCT'], ]); $this->assertSame(3,$this->repository->paginate(1,50)['total']); $this->assertSame(3,$this->repository->summary()['rows']); $this->assertSame([],$this->repository->details('D')['data']); $this->assertSame([],$this->repository->details('E')['data']); } public function test_status_filters_and_child_limit_are_explicit(): void { DB::connection('business_mirror')->table('fixture_install')->where('Id',2)->update(['kfsh'=>'1','Delete'=>'0','StatusId'=>2]); $this->assertSame(1,$this->repository->paginate(1,50,['customer_review'=>'1','delete_status'=>'0','arranged_status'=>'1'])['total']); $rows=[];for($i=1;$i<=101;$i++)$rows[]=['Id'=>$i+10,'guid'=>'B','shopproid'=>'P'.$i]; DB::connection('business_mirror')->table('fixture_items')->insert($rows); $result=$this->repository->details('B'); $this->assertCount(100,$result['data']); $this->assertTrue($result['has_more']); } public function test_unset_status_filters_include_null_values_and_render_explicit_labels(): void { DB::connection('business_mirror')->table('fixture_install')->where('Id',3)->update(['StatusId'=>null]); foreach (['customer_review','finance_review','completed_status','delete_status','arranged_status'] as $key) { $this->assertSame(3,$this->repository->paginate(1,50,[$key=>'0'])['total'],$key); } $other=$this->repository->paginate(1,50,['shop_name'=>'其他店'])['data'][0]; $this->assertSame('未审核',$other['customer_service_review']); $this->assertSame('未审核',$other['finance_review']); $this->assertSame('否',$other['completed_status']); $this->assertSame('未安排',$other['arranged_status']); DB::connection('business_mirror')->table('fixture_install')->where('Id',2)->update([ 'kfsh'=>1, 'cwsh'=>1, 'installComplete'=>1, 'Delete'=>0, 'StatusId'=>1, ]); foreach (['customer_review','finance_review','completed_status','arranged_status'] as $key) { $this->assertSame(1,$this->repository->paginate(1,50,[$key=>'1'])['total'],$key); } } public function test_fee_details_use_customer_review_time_and_latest_main_channel(): void { DB::connection('business_mirror')->table('fixture_install')->where('Id',1)->update(['installCompanyName'=>'旧公司']); DB::connection('business_mirror')->table('fixture_install')->where('Id',2)->update(['installCompanyName'=>'万师傅','costWay'=>null]); DB::connection('business_mirror')->table('fixture_install')->where('Id',3)->update(['installCompanyName'=>'鲁班到家','costWay'=>'银行转账']); DB::connection('business_mirror')->table('fixture_install')->where('Id',4)->update(['installCompanyName'=>'万师傅']); DB::connection('business_mirror')->table('fixture_items')->insert([ ['Id'=>19,'guid'=>'A','shopproid'=>'P0','Sourceno'=>'A-0','kfshTime'=>'2026-08-15 10:00:00','settleItemAmount'=>90.00,'acceptItemAmount'=>80.00], ['Id'=>20,'guid'=>'A','shopproid'=>'P1','Sourceno'=>'A-1','kfshTime'=>'2026-09-01 09:00:00','settleItemAmount'=>60.10,'acceptItemAmount'=>40.00], ['Id'=>21,'guid'=>'A','shopproid'=>'P2','Sourceno'=>'A-1','kfshTime'=>'2026-09-30 18:00:00','settleItemAmount'=>39.90,'acceptItemAmount'=>0.00], ['Id'=>22,'guid'=>'B','shopproid'=>'P3','Sourceno'=>'B-1','kfshTime'=>'2026-09-15 10:00:00','settleItemAmount'=>80.00,'acceptItemAmount'=>70.00], ['Id'=>23,'guid'=>'C','shopproid'=>'P4','Sourceno'=>'C-1','kfshTime'=>'2026-10-01 00:00:00','settleItemAmount'=>500.00,'acceptItemAmount'=>500.00], ['Id'=>24,'guid'=>'D','shopproid'=>'OLD','Sourceno'=>'D-1','kfshTime'=>'2026-09-10 10:00:00','settleItemAmount'=>999.00,'acceptItemAmount'=>999.00], ['Id'=>25,'guid'=>'E','shopproid'=>'NULL','Sourceno'=>'E-1','kfshTime'=>'2026-09-10 10:00:00','settleItemAmount'=>999.00,'acceptItemAmount'=>999.00], ]); $result=$this->repository->feeDetails([ 'customer_review_from'=>'2026-09-01', 'customer_review_to'=>'2026-09-30', ]); $this->assertTrue($result['ready']); $this->assertSame([ ['platform'=>'其他','store_name'=>'测试店','installation_company'=>'万师傅','payment_channel'=>'包安装','amount'=>100.0], ['platform'=>'其他','store_name'=>'其他店','installation_company'=>'鲁班到家','payment_channel'=>'不包安装','amount'=>80.0], ],$result['rows']); $this->assertSame(180.0,$result['total']); $this->assertSame(90.0,$result['previous_total']); $this->assertSame('2026-08-01',$result['previous_from']); $this->assertSame('2026-08-31',$result['previous_to']); $this->assertSame(100.0,$result['change_rate']); } public function test_fee_details_classifies_platform_by_store_name_rules(): void { $stores = [ '阿里巴巴-测试店' => '淘系', '天猫-测试店' => '淘系', '新佰广1688' => '淘系', '淘工厂-测试店' => '淘系', '淘宝-测试店' => '淘系', '抖音-测试店' => '抖音', '拼多多-测试店' => '拼多多', '唯品会MP-测试店' => '唯品会', '唯品会-测试店' => '其他', '得物-测试店' => '得物', '京东-测试店' => '京东', '京喜-测试店' => '京喜', '其他平台店铺' => '其他', ]; $mainRows = []; $itemRows = []; $id = 100; foreach (array_keys($stores) as $store) { $guid = 'platform-' . $id; $mainRows[] = [ 'Id' => $id, 'SourceGuid' => $guid, 'OrderNum' => 'ORDER-' . $id, 'shop_name' => $store, 'SubmitTime' => '2026-09-01', 'ShipTIme' => '2026-09-02', 'installCompanyName' => '万师傅', 'costWay' => null, ]; $itemRows[] = [ 'Id' => $id, 'guid' => $guid, 'shopproid' => 'PRODUCT-' . $id, 'kfshTime' => '2026-09-15 10:00:00', 'settleItemAmount' => 1, ]; $id++; } DB::connection('business_mirror')->table('fixture_install')->insert($mainRows); DB::connection('business_mirror')->table('fixture_items')->insert($itemRows); $result = $this->repository->feeDetails([ 'customer_review_from' => '2026-09-01', 'customer_review_to' => '2026-09-30', ]); $actual = collect($result['rows'])->pluck('platform', 'store_name')->all(); foreach ($stores as $store => $platform) { $this->assertSame($platform, $actual[$store] ?? null, $store); } } public function test_end_date_alone_is_valid_and_invalid_status_is_rejected(): void { request()->merge(['created_to'=>'2026-08-20']); $this->assertSame(['created_to'=>'2026-08-20'],InstallationDetailMirrorRepository::requestFilters()); request()->merge(['arranged_status'=>'unexpected']); $this->expectException(\Illuminate\Validation\ValidationException::class); InstallationDetailMirrorRepository::requestFilters(); } public function test_unified_time_scope_maps_to_the_selected_legacy_date_range(): void { request()->merge([ 'time_scope' => 'finance_review', 'date_from' => '2026-08-01', 'date_to' => '2026-08-31', 'created_from' => '2026-07-01', 'created_to' => '2026-07-31', 'shop_name' => '测试店', ]); $filters = InstallationDetailMirrorRepository::requestFilters(); $this->assertSame('finance_review', $filters['time_scope']); $this->assertSame('2026-08-01', $filters['date_from']); $this->assertSame('2026-08-31', $filters['date_to']); $this->assertSame('2026-08-01', $filters['finance_review_from']); $this->assertSame('2026-08-31', $filters['finance_review_to']); $this->assertSame('测试店', $filters['shop_name']); $this->assertArrayNotHasKey('created_from', $filters); $this->assertArrayNotHasKey('created_to', $filters); } }