Files
vkv/app/Http/Controllers/LogResultController.php

306 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers;
use App\Models\LogResult;
use App\Models\Log;
use App\Models\EvaluationRun;
use App\Models\Round;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Str;
class LogResultController extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
public function __construct()
{
// zápisové operace jen pro přihlášené
$this->middleware('auth:sanctum')->only(['store', 'update', 'destroy']);
}
/**
* Seznam výsledků logů filtrování podle evaluation_run_id,
* log_id, band_id, category_id, status.
*/
public function index(Request $request): JsonResponse
{
$perPage = (int) $request->get('per_page', 100);
$statusParam = $request->get('status');
$isClaimedRequest = $statusParam === 'CLAIMED';
$query = LogResult::query()
->with([
'evaluationRun.ruleSet:id,sixhr_ranking_mode',
'log.round',
'band:id,name,order',
'category:id,name,order',
'powerCategory:id,name,order',
]);
if ($request->filled('evaluation_run_id')) {
$query->where('evaluation_run_id', (int) $request->get('evaluation_run_id'));
}
if ($request->filled('round_id')) {
$roundId = (int) $request->get('round_id');
$query->whereHas('log', function ($q) use ($roundId) {
$q->where('round_id', $roundId);
});
if (! $request->filled('evaluation_run_id') && $request->filled('result_type')) {
$round = Round::find($roundId);
$resultType = strtoupper((string) $request->get('result_type'));
$selectedRunId = null;
if ($round) {
if ($resultType === 'FINAL') {
$selectedRunId = $round->official_evaluation_run_id;
} elseif ($resultType === 'PRELIMINARY') {
$selectedRunId = $round->preliminary_evaluation_run_id;
} elseif ($resultType === 'TEST') {
$selectedRunId = $round->test_evaluation_run_id;
} elseif ($resultType === 'AUTO') {
$selectedRunId = $round->official_evaluation_run_id
?? $round->preliminary_evaluation_run_id;
}
}
if ($selectedRunId) {
$query->where('evaluation_run_id', $selectedRunId);
} else {
$query->whereRaw('1=0');
}
}
if (! $request->filled('evaluation_run_id') && $isClaimedRequest) {
$latestClaimedRunId = EvaluationRun::where('round_id', $roundId)
->where('rules_version', 'CLAIMED')
->orderByDesc('id')
->value('id');
if ($latestClaimedRunId) {
$query->where('evaluation_run_id', $latestClaimedRunId);
}
}
}
if ($request->filled('log_id')) {
$query->where('log_id', (int) $request->get('log_id'));
}
if ($request->filled('band_id')) {
$query->where('band_id', (int) $request->get('band_id'));
}
if ($request->filled('category_id')) {
$query->where('category_id', (int) $request->get('category_id'));
}
if ($request->filled('status') && ! $isClaimedRequest) {
$query->where('status', $statusParam);
}
if ($request->boolean('only_ok', false)) {
$pcallExpr = "UPPER(REPLACE(TRIM(pcall), ' ', ''))";
$query->whereHas('log', function ($q) use ($pcallExpr) {
$q->where(function ($sub) use ($pcallExpr) {
$sub->whereRaw("{$pcallExpr} LIKE ?", ['OK%'])
->orWhereRaw("{$pcallExpr} LIKE ?", ['OL%'])
->orWhereRaw("{$pcallExpr} LIKE ?", ['%/OK%'])
->orWhereRaw("{$pcallExpr} LIKE ?", ['%/OL%']);
});
});
}
// implicitně řadit podle oficiálního skóre
$items = $query
->orderByDesc('official_score')
->paginate($perPage);
if ($this->shouldRedactDeclaredResults($request)) {
$items->getCollection()->transform(function (LogResult $item) {
if ($item->log) {
$item->log->pwwlo = null;
$item->log->codxc = null;
}
return $item;
});
}
return response()->json($items);
}
/**
* Vytvoření záznamu výsledku logu.
* Typicky voláno vyhodnocovačem, ne přímo z UI.
*/
public function store(Request $request): JsonResponse
{
$this->authorize('create', LogResult::class);
$data = $this->validateData($request);
$result = LogResult::create($data);
$result->load([
'evaluationRun.ruleSet:id,sixhr_ranking_mode',
'log',
'band:id,name,order',
'category:id,name,order',
'powerCategory:id,name,order',
]);
return response()->json($result, 201);
}
/**
* Detail jednoho výsledku.
*/
public function show(LogResult $logResult): JsonResponse
{
$logResult->load([
'evaluationRun.ruleSet:id,sixhr_ranking_mode',
'log.round',
'band:id,name,order',
'category:id,name,order',
'powerCategory:id,name,order',
]);
$request = request();
if ($this->shouldRedactDeclaredResults($request, $logResult)) {
if ($logResult->log) {
$logResult->log->pwwlo = null;
$logResult->log->codxc = null;
}
}
return response()->json($logResult);
}
/**
* Aktualizace výsledku (partial).
* Typicky pro ruční korekci statutu / poznámky.
*/
public function update(Request $request, LogResult $logResult): JsonResponse
{
$this->authorize('update', $logResult);
$data = $this->validateData($request, partial: true);
$logResult->fill($data);
$logResult->save();
$logResult->load([
'evaluationRun.ruleSet:id,sixhr_ranking_mode',
'log',
'band:id,name,order',
'category:id,name,order',
'powerCategory:id,name,order',
]);
return response()->json($logResult);
}
/**
* Smazání výsledku.
*/
public function destroy(LogResult $logResult): JsonResponse
{
$this->authorize('delete', $logResult);
$logResult->delete();
return response()->json(null, 204);
}
/**
* Validace pro store / update.
*/
protected function validateData(Request $request, bool $partial = false): array
{
$required = $partial ? 'sometimes' : 'required';
return $request->validate([
'evaluation_run_id' => [$required, 'integer', 'exists:evaluation_runs,id'],
'log_id' => [$required, 'integer', 'exists:logs,id'],
'band_id' => ['sometimes', 'nullable', 'integer', 'exists:bands,id'],
'category_id' => ['sometimes', 'nullable', 'integer', 'exists:categories,id'],
'power_category_id' => ['sometimes', 'nullable', 'integer', 'exists:power_categories,id'],
'claimed_qso_count' => ['sometimes', 'nullable', 'integer', 'min:0'],
'claimed_score' => ['sometimes', 'nullable', 'integer', 'min:0'],
'valid_qso_count' => ['sometimes', 'integer', 'min:0'],
'dupe_qso_count' => ['sometimes', 'integer', 'min:0'],
'busted_qso_count' => ['sometimes', 'integer', 'min:0'],
'other_error_qso_count' => ['sometimes', 'integer', 'min:0'],
'total_qso_count' => ['sometimes', 'integer', 'min:0'],
'discarded_qso_count' => ['sometimes', 'integer', 'min:0'],
'discarded_points' => ['sometimes', 'integer'],
'discarded_qso_percent' => ['sometimes', 'numeric', 'min:0'],
'unique_qso_count' => ['sometimes', 'integer', 'min:0'],
'official_score' => ['sometimes', 'integer'],
'penalty_score' => ['sometimes', 'integer'],
'base_score' => ['sometimes', 'integer'],
'multiplier_count' => ['sometimes', 'integer', 'min:0'],
'multiplier_score' => ['sometimes', 'integer'],
'score_per_qso' => ['sometimes', 'numeric', 'min:0'],
'rank_overall' => ['sometimes', 'nullable', 'integer', 'min:1'],
'rank_in_category' => ['sometimes', 'nullable', 'integer', 'min:1'],
'status' => ['sometimes', 'string', 'max:20'],
'status_reason' => ['sometimes', 'nullable', 'string'],
]);
}
private function shouldRedactDeclaredResults(Request $request, ?LogResult $logResult = null): bool
{
$user = $request->user();
if ($user && $user->is_admin) {
return false;
}
if ($logResult) {
$run = $logResult->evaluationRun;
$isClaimed = $run && strtoupper((string) $run->rules_version) === 'CLAIMED';
if (! $isClaimed) {
return false;
}
return ! $this->hasOfficialResultsPublished($logResult->log?->round);
}
if ($request->get('status') !== 'CLAIMED') {
return false;
}
$round = null;
if ($request->filled('round_id')) {
$round = Round::find((int) $request->get('round_id'));
} elseif ($request->filled('evaluation_run_id')) {
$run = EvaluationRun::find((int) $request->get('evaluation_run_id'));
$round = $run?->round;
} elseif ($request->filled('log_id')) {
$log = Log::with('round')->find((int) $request->get('log_id'));
$round = $log?->round;
}
return ! $this->hasOfficialResultsPublished($round);
}
private function hasOfficialResultsPublished(?Round $round): bool
{
if (! $round || ! $round->official_evaluation_run_id) {
return false;
}
return EvaluationRun::query()
->where('id', $round->official_evaluation_run_id)
->where('status', 'SUCCEEDED')
->where('result_type', 'FINAL')
->exists();
}
}