create([ 'username' => 'admin_list', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ])); $competition = Competition::query()->create([ 'slug' => 'xxf', 'name' => '测试赛', 'published' => true, ]); $active = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138001'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '有效项目', ]); $deleted = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138002'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '已删除项目', ]); $deleted->forceFill(['deleted_at' => now()])->save(); $response = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted") ->assertOk(); $ids = collect($response->json('data'))->pluck('id')->all(); $this->assertContains($active->id, $ids); $this->assertNotContains($deleted->id, $ids); } public function test_admin_application_list_filters_by_review_result(): void { Sanctum::actingAs(AdminUser::query()->create([ 'username' => 'admin_filter', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ])); $competition = Competition::query()->create([ 'slug' => 'xxf-filter', 'name' => '筛选测试赛', 'published' => true, ]); $passed = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138011'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '已通过项目', 'review_result' => 'passed', ]); Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138012'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '待评定项目', 'review_result' => null, ]); $response = $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&review_result=passed") ->assertOk(); $ids = collect($response->json('data'))->pluck('id')->all(); $this->assertContains($passed->id, $ids); $this->assertCount(1, $ids); } public function test_admin_application_list_survives_schema_without_list_flag(): void { Sanctum::actingAs(AdminUser::query()->create([ 'username' => 'admin_schema_list', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ])); $competition = Competition::query()->create([ 'slug' => 'xxf-schema-list', 'name' => '缺 list 字段赛', 'published' => true, ]); $schema = FormSchemaDefinition::query()->create([ 'competition_id' => $competition->id, 'purpose' => FormSchemaDefinition::PURPOSE_SIGNUP, 'name' => '报名表', 'version' => 1, 'is_published' => true, 'schema_json' => [ [ 'key' => 'contact_email', 'type' => 'email', 'label' => '邮箱', ], [ 'key' => 'event_location', 'type' => 'select', 'label' => '意向参赛地点', ], ], ]); $competition->forceFill(['form_schema_id' => $schema->id])->save(); Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138021'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '缺 list 项目', 'review_eligible' => true, ]); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?page=1&per_page=15&status=submitted&review_eligible=true") ->assertOk() ->assertJsonPath('data.0.project_name', '缺 list 项目'); } public function test_admin_application_list_keeps_files_count_when_sorting_by_team_avg(): void { Sanctum::actingAs(AdminUser::query()->create([ 'username' => 'admin_files_count', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ])); $competition = Competition::query()->create([ 'slug' => 'xxf-files-count', 'name' => '附件计数赛', 'published' => true, ]); $application = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138031'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '有附件项目', ]); ApplicationFile::query()->create([ 'application_id' => $application->id, 'kind' => 'plan', 'disk' => 'public', 'path' => 'applications/plan.pdf', 'original_name' => '商业计划书.pdf', 'size' => 1024, 'mime' => 'application/pdf', ]); ApplicationFile::query()->create([ 'application_id' => $application->id, 'kind' => 'supporting', 'disk' => 'public', 'path' => 'applications/extra.pdf', 'original_name' => '补充材料.pdf', 'size' => 2048, 'mime' => 'application/pdf', ]); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&sort_by=team_avg&sort_dir=desc") ->assertOk() ->assertJsonPath('data.0.id', $application->id) ->assertJsonPath('data.0.files_count', 2); } public function test_admin_application_list_rounds_team_avg_to_two_decimals(): void { Sanctum::actingAs(AdminUser::query()->create([ 'username' => 'admin_avg_round', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ])); $competition = Competition::query()->create([ 'slug' => 'xxf-avg-round', 'name' => '均分取整赛', 'published' => true, ]); $application = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138032'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '小数均分项目', ]); $reviewerA = Reviewer::query()->create([ 'mobile' => '13900139201', 'username' => 'rev_avg_a', 'name' => '评委A', 'status' => 'active', 'password_hash' => 'unused', ]); $reviewerB = Reviewer::query()->create([ 'mobile' => '13900139202', 'username' => 'rev_avg_b', 'name' => '评委B', 'status' => 'active', 'password_hash' => 'unused', ]); ApplicationReviewScore::query()->create([ 'application_id' => $application->id, 'reviewer_id' => $reviewerA->id, 'payload_json' => ['line_total' => 80.33], 'line_total' => 80.33, ]); ApplicationReviewScore::query()->create([ 'application_id' => $application->id, 'reviewer_id' => $reviewerB->id, 'payload_json' => ['line_total' => 80.34], 'line_total' => 80.34, ]); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted") ->assertOk() ->assertJsonPath('data.0.id', $application->id) ->assertJsonPath('data.0.team_sum', 160.67) ->assertJsonPath('data.0.team_avg', 80.34); } public function test_review_progress_counts_region_reviewers_not_track_rows(): void { Sanctum::actingAs(AdminUser::query()->create([ 'username' => 'admin_progress_region', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ])); $competition = Competition::query()->create([ 'slug' => 'hsxt-progress', 'name' => '赛区进程赛', 'published' => true, ]); $suzhouA = Reviewer::query()->create([ 'mobile' => '13900139301', 'username' => 'suzhou_a', 'name' => '苏州甲', 'status' => 'active', 'password_hash' => 'unused', ]); $suzhouB = Reviewer::query()->create([ 'mobile' => '13900139302', 'username' => 'suzhou_b', 'name' => '苏州乙', 'status' => 'active', 'password_hash' => 'unused', ]); $shanghai = Reviewer::query()->create([ 'mobile' => '13900139303', 'username' => 'shanghai_a', 'name' => '上海甲', 'status' => 'active', 'password_hash' => 'unused', ]); foreach (['guang_xinpian', 'other'] as $track) { ReviewerScope::query()->create([ 'competition_id' => $competition->id, 'reviewer_id' => $suzhouB->id, 'track_code' => $track, 'event_location' => 'suzhou', ]); ReviewerScope::query()->create([ 'competition_id' => $competition->id, 'reviewer_id' => $shanghai->id, 'track_code' => $track, 'event_location' => 'shanghai', ]); } ReviewerScope::query()->create([ 'competition_id' => $competition->id, 'reviewer_id' => $suzhouA->id, 'track_code' => 'guang_xinpian', 'event_location' => 'suzhou', ]); $twoReviewers = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138301'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '双评委苏州项目', 'track' => 'other', 'event_location' => 'suzhou', 'review_eligible' => true, ]); ApplicationReviewScore::query()->create([ 'application_id' => $twoReviewers->id, 'reviewer_id' => $suzhouA->id, 'payload_json' => ['line_total' => 85.43], 'line_total' => 85.43, ]); $oneReviewer = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138302'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '单评委苏州项目', 'track' => 'other', 'event_location' => 'suzhou', 'review_eligible' => true, ]); $first = collect($this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&review_eligible=true") ->assertOk() ->json('data'))->keyBy('id'); $this->assertSame('1/2', $first[$twoReviewers->id]['review_progress']); $this->assertFalse($first[$twoReviewers->id]['review_fully_completed']); $this->assertSame(85.43, $first[$twoReviewers->id]['team_sum']); ReviewerScope::query()->where('reviewer_id', $suzhouB->id)->delete(); ApplicationReviewScore::query()->create([ 'application_id' => $oneReviewer->id, 'reviewer_id' => $suzhouA->id, 'payload_json' => ['line_total' => 90], 'line_total' => 90, ]); $second = collect($this->getJson("/api/v1/admin/competitions/{$competition->id}/applications?status=submitted&review_eligible=true") ->assertOk() ->json('data'))->keyBy('id'); $this->assertSame('1/1', $second[$oneReviewer->id]['review_progress']); $this->assertTrue($second[$oneReviewer->id]['review_fully_completed']); $this->assertSame(90, $second[$oneReviewer->id]['team_sum']); } }