|
|
|
|
@ -5,6 +5,7 @@ namespace App\Services;
|
|
|
|
|
use App\Models\Application;
|
|
|
|
|
use App\Models\ApplicationFile;
|
|
|
|
|
use App\Models\Competition;
|
|
|
|
|
use App\Models\ReviewerScope;
|
|
|
|
|
use App\Support\ProjectCode;
|
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
|
@ -329,16 +330,22 @@ class AdminApplicationExportService
|
|
|
|
|
'项目简介',
|
|
|
|
|
'提交时间',
|
|
|
|
|
'附件数量',
|
|
|
|
|
'进程',
|
|
|
|
|
'评分详情',
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
$reviewersByTrack = $this->reviewersByTrackForCompetition($competition);
|
|
|
|
|
|
|
|
|
|
$chunkSize = max(20, (int) config('contest.export.chunk_size', 20));
|
|
|
|
|
$this->buildFilteredQuery($competition, $filters)
|
|
|
|
|
->with(['signupChannel'])
|
|
|
|
|
->with(['signupChannel', 'reviewScores.reviewer'])
|
|
|
|
|
->withCount('files')
|
|
|
|
|
->orderBy('id')
|
|
|
|
|
->chunkById($chunkSize, function ($applications) use ($writer, $trackTitles): void {
|
|
|
|
|
->chunkById($chunkSize, function ($applications) use ($writer, $trackTitles, $reviewersByTrack): void {
|
|
|
|
|
foreach ($applications as $app) {
|
|
|
|
|
$writer->addRow(Row::fromValues($this->applicationExportRow($app, $trackTitles)));
|
|
|
|
|
$writer->addRow(Row::fromValues(
|
|
|
|
|
$this->applicationExportRow($app, $trackTitles, $reviewersByTrack)
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}, 'id');
|
|
|
|
|
|
|
|
|
|
@ -372,16 +379,73 @@ class AdminApplicationExportService
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 按赛道预加载评委名单(含姓名),用于导出进程与评分详情。
|
|
|
|
|
*
|
|
|
|
|
* @return Collection<string, list<array{id: int, name: string}>>
|
|
|
|
|
*/
|
|
|
|
|
private function reviewersByTrackForCompetition(Competition $competition): Collection
|
|
|
|
|
{
|
|
|
|
|
$grouped = [];
|
|
|
|
|
ReviewerScope::query()
|
|
|
|
|
->with('reviewer')
|
|
|
|
|
->where('competition_id', $competition->id)
|
|
|
|
|
->orderBy('id')
|
|
|
|
|
->get()
|
|
|
|
|
->each(function (ReviewerScope $scope) use (&$grouped): void {
|
|
|
|
|
$track = (string) ($scope->track_code ?? '');
|
|
|
|
|
if ($track === '') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$reviewerId = (int) $scope->reviewer_id;
|
|
|
|
|
$name = trim((string) ($scope->reviewer?->name ?? ''));
|
|
|
|
|
if ($name === '') {
|
|
|
|
|
$name = '评委#'.$reviewerId;
|
|
|
|
|
}
|
|
|
|
|
$grouped[$track] ??= [];
|
|
|
|
|
$grouped[$track][$reviewerId] = [
|
|
|
|
|
'id' => $reviewerId,
|
|
|
|
|
'name' => $name,
|
|
|
|
|
];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return collect($grouped)->map(fn (array $rows) => array_values($rows));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param Collection<string, string> $trackTitles
|
|
|
|
|
* @param Collection<string, list<array{id: int, name: string}>> $reviewersByTrack
|
|
|
|
|
* @return list<scalar|null>
|
|
|
|
|
*/
|
|
|
|
|
private function applicationExportRow(Application $app, Collection $trackTitles): array
|
|
|
|
|
private function applicationExportRow(Application $app, Collection $trackTitles, Collection $reviewersByTrack): array
|
|
|
|
|
{
|
|
|
|
|
$channel = $app->signupChannel
|
|
|
|
|
? $app->signupChannel->channel_name.'('.$app->signupChannel->channel_code.')'
|
|
|
|
|
: ($app->signup_channel_code ?: '');
|
|
|
|
|
|
|
|
|
|
$trackReviewers = $reviewersByTrack->get((string) ($app->track ?? '')) ?? [];
|
|
|
|
|
$required = count($trackReviewers);
|
|
|
|
|
$scoresByReviewerId = [];
|
|
|
|
|
if ($app->relationLoaded('reviewScores')) {
|
|
|
|
|
foreach ($app->reviewScores as $score) {
|
|
|
|
|
$scoresByReviewerId[(int) $score->reviewer_id] = $score;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$completed = count($scoresByReviewerId);
|
|
|
|
|
$progress = $required > 0 ? ($completed.'/'.$required) : '-';
|
|
|
|
|
|
|
|
|
|
$scoreDetails = collect($trackReviewers)
|
|
|
|
|
->map(function (array $reviewer) use ($scoresByReviewerId): string {
|
|
|
|
|
$name = $reviewer['name'];
|
|
|
|
|
$score = $scoresByReviewerId[$reviewer['id']] ?? null;
|
|
|
|
|
if ($score === null) {
|
|
|
|
|
return $name.' 未评审';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $name.' '.$this->formatExportScore($score->line_total);
|
|
|
|
|
})
|
|
|
|
|
->implode(';');
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
ProjectCode::resolve($app),
|
|
|
|
|
$app->status === 'submitted' ? '已提交' : '草稿',
|
|
|
|
|
@ -400,9 +464,23 @@ class AdminApplicationExportService
|
|
|
|
|
$app->intro ?? '',
|
|
|
|
|
$app->submitted_at?->format('Y-m-d H:i:s') ?? '',
|
|
|
|
|
(int) ($app->files_count ?? $app->files->count()),
|
|
|
|
|
$progress,
|
|
|
|
|
$scoreDetails,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function formatExportScore(mixed $value): string
|
|
|
|
|
{
|
|
|
|
|
if ($value === null || $value === '') {
|
|
|
|
|
return '—';
|
|
|
|
|
}
|
|
|
|
|
if (! is_numeric($value)) {
|
|
|
|
|
return (string) $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rtrim(rtrim(number_format((float) $value, 2, '.', ''), '0'), '.') ?: '0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param array<string, mixed> $filters
|
|
|
|
|
*/
|
|
|
|
|
|