middleware('auth:sanctum')->only(['store', 'update', 'destroy']); } /** * Seznam QSO výsledků. * Filtrování podle evaluation_run_id, log_qso_id, log_id, call_like, matched_qso_id, * error_code, is_valid, is_duplicate, is_nil, only_ok. */ public function index(Request $request): JsonResponse { $perPage = (int) $request->get('per_page', 200); $evalRunId = $request->filled('evaluation_run_id') ? (int) $request->get('evaluation_run_id') : null; $query = QsoResult::query() ->with(['evaluationRun', 'logQso', 'matchedQso']) ->when($evalRunId, function ($q) use ($evalRunId) { $q->with(['workingQso' => function ($wq) use ($evalRunId) { $wq->where('evaluation_run_id', $evalRunId); }]); }); if ($evalRunId !== null) { $query->where('evaluation_run_id', $evalRunId); } if ($request->filled('log_qso_id')) { $query->where('log_qso_id', (int) $request->get('log_qso_id')); } if ($request->filled('log_id')) { $logId = (int) $request->get('log_id'); $query->whereHas('logQso', function ($q) use ($logId) { $q->where('log_id', $logId); }); } if ($request->filled('call_like')) { $raw = strtoupper((string) $request->get('call_like')); $pattern = str_replace(['*', '?'], ['%', '_'], $raw); if (strpos($pattern, '%') === false && strpos($pattern, '_') === false) { $pattern = '%' . $pattern . '%'; } $query->where(function ($q) use ($pattern) { $q->whereHas('logQso', function ($qq) use ($pattern) { $qq->whereRaw('UPPER(my_call) LIKE ?', [$pattern]) ->orWhereRaw('UPPER(dx_call) LIKE ?', [$pattern]); })->orWhereHas('matchedQso', function ($qq) use ($pattern) { $qq->whereRaw('UPPER(my_call) LIKE ?', [$pattern]) ->orWhereRaw('UPPER(dx_call) LIKE ?', [$pattern]); }); }); } if ($request->filled('matched_qso_id')) { $query->where('matched_qso_id', (int) $request->get('matched_qso_id')); } if ($request->filled('error_code')) { $query->where('error_code', $request->get('error_code')); } if ($request->filled('is_valid')) { $query->where( 'is_valid', filter_var($request->get('is_valid'), FILTER_VALIDATE_BOOL) ); } if ($request->filled('is_duplicate')) { $query->where( 'is_duplicate', filter_var($request->get('is_duplicate'), FILTER_VALIDATE_BOOL) ); } if ($request->filled('is_nil')) { $query->where( 'is_nil', filter_var($request->get('is_nil'), FILTER_VALIDATE_BOOL) ); } if ($request->filled('is_time_out_of_window')) { $query->where( 'is_time_out_of_window', filter_var($request->get('is_time_out_of_window'), FILTER_VALIDATE_BOOL) ); } if (filter_var($request->get('only_problems'), FILTER_VALIDATE_BOOL)) { $query->where(function ($q) { $q->where(function ($qq) { $qq->whereNotNull('error_code') ->where('error_code', '!=', 'OK'); }) ->orWhere('is_nil', true) ->orWhere('is_duplicate', true) ->orWhere('is_busted_call', true) ->orWhere('is_busted_exchange', true) ->orWhere('is_time_out_of_window', true); }); } if (filter_var($request->get('only_ok'), FILTER_VALIDATE_BOOL)) { $query->where(function ($q) { $q->whereNull('error_code') ->orWhere('error_code', 'OK'); }) ->where('is_nil', false) ->where('is_duplicate', false) ->where('is_busted_call', false) ->where('is_busted_exchange', false) ->where('is_time_out_of_window', false); } if (filter_var($request->get('missing_locator'), FILTER_VALIDATE_BOOL)) { $query->whereHas('workingQso', function ($q) use ($evalRunId) { if ($evalRunId !== null) { $q->where('evaluation_run_id', $evalRunId); } $q->whereNull('loc_norm') ->orWhereNull('rloc_norm') ->orWhereJsonContains('errors', 'INVALID_LOCATOR') ->orWhereJsonContains('errors', 'INVALID_RLOCATOR'); }); } $items = $query ->orderBy('evaluation_run_id') ->orderBy('log_qso_id') ->paginate($perPage); return response()->json($items); } /** * Vytvoření QSO výsledku. * Typicky voláno vyhodnocovačem, ne přímo z UI. */ public function store(Request $request): JsonResponse { $this->authorize('create', QsoResult::class); $data = $this->validateData($request); $result = QsoResult::create($data); $result->load([ 'evaluationRun', 'logQso', 'matchedQso', 'workingQso' => function ($q) use ($result) { $q->where('evaluation_run_id', $result->evaluation_run_id); }, ]); return response()->json($result, 201); } /** * Detail jednoho QSO výsledku. */ public function show(QsoResult $qsoResult): JsonResponse { $qsoResult->load([ 'evaluationRun', 'logQso', 'matchedQso', 'workingQso' => function ($q) use ($qsoResult) { $q->where('evaluation_run_id', $qsoResult->evaluation_run_id); }, ]); return response()->json($qsoResult); } /** * Aktualizace QSO výsledku (partial update). * Praktické pro ruční korekce / override. */ public function update(Request $request, QsoResult $qsoResult): JsonResponse { $this->authorize('update', $qsoResult); $data = $this->validateData($request, partial: true); $qsoResult->fill($data); $qsoResult->save(); $qsoResult->load([ 'evaluationRun', 'logQso', 'matchedQso', 'workingQso' => function ($q) use ($qsoResult) { $q->where('evaluation_run_id', $qsoResult->evaluation_run_id); }, ]); return response()->json($qsoResult); } /** * Smazání QSO výsledku. */ public function destroy(QsoResult $qsoResult): JsonResponse { $this->authorize('delete', $qsoResult); $qsoResult->delete(); return response()->json(null, 204); } /** * Validace vstupu 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_qso_id' => [$required, 'integer', 'exists:log_qsos,id'], 'is_valid' => ['sometimes', 'boolean'], 'is_duplicate' => ['sometimes', 'boolean'], 'is_nil' => ['sometimes', 'boolean'], 'is_busted_call' => ['sometimes', 'boolean'], 'is_busted_rst' => ['sometimes', 'boolean'], 'is_busted_exchange' => ['sometimes', 'boolean'], 'is_time_out_of_window' => ['sometimes', 'boolean'], 'points' => ['sometimes', 'integer'], 'penalty_points' => ['sometimes', 'integer'], 'distance_km' => ['sometimes', 'nullable', 'integer', 'min:0'], 'wwl' => ['sometimes', 'nullable', 'string', 'max:6'], 'dxcc' => ['sometimes', 'nullable', 'string', 'max:10'], 'country' => ['sometimes', 'nullable', 'string', 'max:100'], 'section' => ['sometimes', 'nullable', 'string', 'max:50'], 'matched_qso_id' => ['sometimes', 'nullable', 'integer', 'exists:log_qsos,id'], 'matched_log_qso_id' => ['sometimes', 'nullable', 'integer', 'exists:log_qsos,id'], 'match_confidence' => ['sometimes', 'nullable', 'string', 'max:20'], 'error_code' => ['sometimes', 'nullable', 'string', 'max:50'], 'error_side' => ['sometimes', 'nullable', 'string', 'max:10'], 'error_detail' => ['sometimes', 'nullable', 'string'], ]); } }