create([ 'username' => 'admin_export', 'password_hash' => 'unused', 'name' => '管理员', 'status' => 'active', ]); $competition = Competition::query()->create([ 'slug' => 'xxf', 'name' => '测试赛', 'published' => true, ]); $application = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138002'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '项目A', ]); Sanctum::actingAs($admin); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications/export/meta?mode=xlsx") ->assertOk() ->assertJsonPath('mode', AdminApplicationExportService::MODE_XLSX) ->assertJsonPath('includes_attachments', false) ->assertJsonPath('part_count', 1); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications/export/meta?mode=all") ->assertOk() ->assertJsonPath('mode', AdminApplicationExportService::MODE_ALL) ->assertJsonPath('includes_attachments', true); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications/export/meta?mode=selected&application_ids[]={$application->id}") ->assertOk() ->assertJsonPath('mode', AdminApplicationExportService::MODE_SELECTED) ->assertJsonPath('includes_attachments', true); } public function test_selected_export_requires_application_ids(): void { $admin = AdminUser::query()->create([ 'username' => 'admin_export2', 'password_hash' => 'unused', 'name' => '管理员2', 'status' => 'active', ]); $competition = Competition::query()->create([ 'slug' => 'xxf2', 'name' => '测试赛2', 'published' => true, ]); Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138004'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', ]); Sanctum::actingAs($admin); $this->getJson("/api/v1/admin/competitions/{$competition->id}/applications/export/meta?mode=selected") ->assertStatus(422) ->assertJsonValidationErrors(['application_ids']); } public function test_xlsx_export_includes_progress_and_score_details(): void { $admin = AdminUser::query()->create([ 'username' => 'admin_export3', 'password_hash' => 'unused', 'name' => '管理员3', 'status' => 'active', ]); $competition = Competition::query()->create([ 'slug' => 'xxf-export-score', 'name' => '导出评分赛', 'published' => true, ]); CompetitionTrack::query()->create([ 'competition_id' => $competition->id, 'track_code' => 'track_a', 'title' => '赛道A', 'sort' => 1, 'is_enabled' => true, ]); $reviewer1 = Reviewer::query()->create([ 'mobile' => '13900139101', 'username' => 'rev_export_1', 'name' => '评委甲', 'status' => 'active', 'password_hash' => 'unused', ]); $reviewer2 = Reviewer::query()->create([ 'mobile' => '13900139102', 'username' => 'rev_export_2', 'name' => '评委乙', 'status' => 'active', 'password_hash' => 'unused', ]); ReviewerScope::query()->create([ 'competition_id' => $competition->id, 'reviewer_id' => $reviewer1->id, 'track_code' => 'track_a', ]); ReviewerScope::query()->create([ 'competition_id' => $competition->id, 'reviewer_id' => $reviewer2->id, 'track_code' => 'track_a', ]); $application = Application::query()->create([ 'user_id' => User::query()->create(['mobile' => '13800138111'])->id, 'competition_id' => $competition->id, 'status' => 'submitted', 'project_name' => '导出评分项目', 'track' => 'track_a', ]); ApplicationReviewScore::query()->create([ 'application_id' => $application->id, 'reviewer_id' => $reviewer1->id, 'payload_json' => ['line_total' => 88.5], 'line_total' => 88.5, ]); Sanctum::actingAs($admin); $service = app(AdminApplicationExportService::class); $xlsxPath = $service->buildXlsxExportPath($competition, ['status' => 'submitted']); $this->assertFileExists($xlsxPath); $reader = new Reader; $reader->open($xlsxPath); $rows = []; foreach ($reader->getSheetIterator() as $sheet) { foreach ($sheet->getRowIterator() as $row) { $rows[] = $row->toArray(); } break; } $reader->close(); @unlink($xlsxPath); $this->assertGreaterThanOrEqual(2, count($rows)); $header = $rows[0]; $this->assertContains('进程', $header); $this->assertContains('总分', $header); $this->assertContains('平均分', $header); $this->assertContains('评分详情', $header); $progressIdx = array_search('进程', $header, true); $totalIdx = array_search('总分', $header, true); $avgIdx = array_search('平均分', $header, true); $detailIdx = array_search('评分详情', $header, true); $this->assertNotFalse($progressIdx); $this->assertNotFalse($totalIdx); $this->assertNotFalse($avgIdx); $this->assertNotFalse($detailIdx); $data = $rows[1]; $this->assertSame('1/2', (string) $data[$progressIdx]); $this->assertSame('待评审', (string) $data[$totalIdx]); $this->assertSame('-', (string) $data[$avgIdx]); $this->assertStringContainsString('评委甲 88.5', (string) $data[$detailIdx]); $this->assertStringContainsString('评委乙 未评审', (string) $data[$detailIdx]); ApplicationReviewScore::query()->create([ 'application_id' => $application->id, 'reviewer_id' => $reviewer2->id, 'payload_json' => ['line_total' => 91.5], 'line_total' => 91.5, ]); $xlsxPath2 = $service->buildXlsxExportPath($competition, ['status' => 'submitted']); $this->assertFileExists($xlsxPath2); $reader2 = new Reader; $reader2->open($xlsxPath2); $rows2 = []; foreach ($reader2->getSheetIterator() as $sheet) { foreach ($sheet->getRowIterator() as $row) { $rows2[] = $row->toArray(); } break; } $reader2->close(); @unlink($xlsxPath2); $data2 = $rows2[1]; $this->assertSame('2/2', (string) $data2[$progressIdx]); $this->assertSame('180', (string) $data2[$totalIdx]); $this->assertSame('90', (string) $data2[$avgIdx]); } }