instance(QuoteManagementSnapshot::class, new QuoteManagementSnapshot('/nonexistent-batch-review-test.json')); $this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail()); config(['courier_onboarding.allow_same_reviewer' => false]); } protected function reviewer() { $reviewer = user_model_class()::create(['name'=>'批量审核员','email'=>'batch-'.uniqid().'@example.com','password'=>'local-test-password','locale'=>'zh-CN','enabled'=>1,'landing_page'=>'dashboard','created_from'=>'core::test']); $reviewer->companies()->attach($this->company->id); $reviewer->attachRole(Role::where('name','admin')->firstOrFail()); $reviewer->attachPermission(Permission::where('name','read-admin-panel')->firstOrFail()); return $reviewer; } protected function pending(string $name, ?int $creator = null): CourierQuoteChange { $creator ??= $this->user->id; $entry = app(CourierStockQuoteEntryService::class)->createPrepared( ['quote_type'=>'标准快递报价','effective_date'=>'2026-08-01','summary'=>'批量审核测试','billing_rules'=>'保留原报价规则'], $creator, ['code'=>'TEST-'.$name,'name'=>$name], ['sheet_name'=>'原报价','source_range'=>'A1:B2','values'=>[['省份','运费'],['江苏',5]],'formulas'=>[[],[]]] ); return app(CourierQuoteChangeService::class)->submit($entry->change, $creator); } public function test_batch_approves_selected_stock_quotes_and_does_not_mark_historical_files_verified(): void { $this->loginAs(); $one=$this->pending('快递甲');$two=$this->pending('快递乙');$untouched=$this->pending('未选快递'); $this->loginAs($this->reviewer())->post(route('business.courier-quote-changes.batch-approve'), ['ids'=>[$one->id,$two->id],'stock_rules_confirmed'=>1,'stock_signature_verified'=>1]) ->assertRedirect(route('business.courier-quote-changes.index',['page'=>1])) ->assertSessionHas('success','批量审核完成:成功 2 条,未通过 0 条。'); $this->assertSame('approved',$one->fresh()->status); $this->assertSame('approved',$two->fresh()->status); $this->assertSame('pending_review',$untouched->fresh()->status); $this->assertDatabaseCount('business_courier_quote_versions',2); foreach(CourierQuoteVersion::all() as $version) $this->assertSame('存量报价,审核生效(无需签章)',$version->signature_status); $this->assertSame(1,$one->events()->where('event_type','approved')->count()); $this->assertSame(1,$two->events()->where('event_type','approved')->count()); Http::assertNothingSent(); } public function test_partial_batch_keeps_self_review_stale_and_other_company_records_unchanged(): void { $this->loginAs();$reviewer=$this->reviewer(); $good=$this->pending('正常');$own=$this->pending('自审',$reviewer->id);$old=$this->pending('已审核');$foreign=$this->pending('外公司'); app(CourierQuoteChangeService::class)->approve($old,$reviewer->id); DB::table('business_courier_quote_changes')->where('id',$foreign->id)->update(['company_id'=>$this->company->id+1000]); $this->loginAs($reviewer)->post(route('business.courier-quote-changes.batch-approve'), ['ids'=>[$good->id,$own->id,$old->id,$foreign->id,99999999],'stock_rules_confirmed'=>1]) ->assertSessionHas('success','批量审核完成:成功 1 条,未通过 4 条。'); $this->assertSame('approved',$good->fresh()->status); $this->assertSame('pending_review',$own->fresh()->status); $this->assertDatabaseCount('business_courier_quote_versions',2); $this->assertSame('pending_review',DB::table('business_courier_quote_changes')->where('id',$foreign->id)->value('status')); } public function test_batch_requires_confirmation_nonempty_distinct_bounded_ids(): void { $this->loginAs();$change=$this->pending('校验'); $this->withExceptionHandling()->loginAs($this->reviewer()); foreach ([['ids'=>[$change->id]],['ids'=>[],'stock_rules_confirmed'=>1],['ids'=>[$change->id,$change->id],'stock_rules_confirmed'=>1],['ids'=>range(1,101),'stock_rules_confirmed'=>1]] as $input) { $this->post(route('business.courier-quote-changes.batch-approve'),$input)->assertSessionHasErrors(); } $this->assertSame('pending_review',$change->fresh()->status); $this->assertDatabaseCount('business_courier_quote_versions',0); } public function test_non_reviewer_cannot_use_batch_endpoint(): void { $this->loginAs();$change=$this->pending('权限');$viewer=$this->reviewer();$viewer->detachRole(Role::where('name','admin')->firstOrFail()); $this->withExceptionHandling()->loginAs($viewer)->post(route('business.courier-quote-changes.batch-approve'),['ids'=>[$change->id],'stock_rules_confirmed'=>1])->assertForbidden(); $this->assertSame('pending_review',$change->fresh()->status); } public function test_stale_model_cannot_approve_twice(): void { $this->loginAs();$one=$this->pending('并发');$stale=CourierQuoteChange::findOrFail($one->id);$reviewer=$this->reviewer(); app(CourierQuoteChangeService::class)->approve($one,$reviewer->id); try { app(CourierQuoteChangeService::class)->approve($stale,$reviewer->id); $this->fail('过期状态不应重复审核'); } catch(ValidationException $exception){$this->assertArrayHasKey('status',$exception->errors());} $this->assertDatabaseCount('business_courier_quote_versions',1); $this->assertSame(1,$one->events()->where('event_type','approved')->count()); } }