feat(scanner): update scan handling to return results for invalid QR data and other scan conditions
This commit is contained in:
@@ -11,13 +11,10 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
class ScannerTicketService
|
||||
{
|
||||
private const INVALID_QR_MESSAGE = 'El QR proporcionado es inválido.';
|
||||
|
||||
/**
|
||||
* @param array{q?: string|null, page?: int, per_page?: int} $filters
|
||||
* @return LengthAwarePaginator<ScanAttempt>
|
||||
@@ -188,13 +185,10 @@ class ScannerTicketService
|
||||
if (! is_string($scannedData) || ! Str::isUuid($scannedData)) {
|
||||
$this->resolveScanAttempt($scanAttempt, ScanAttemptResult::InvalidQr);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'data' => self::INVALID_QR_MESSAGE,
|
||||
]);
|
||||
return $scanAttempt->refresh();
|
||||
}
|
||||
|
||||
$ticketId = null;
|
||||
$failureResult = null;
|
||||
|
||||
try {
|
||||
return DB::transaction(function () use (
|
||||
@@ -202,7 +196,6 @@ class ScannerTicketService
|
||||
$scannedData,
|
||||
$scanAttempt,
|
||||
&$ticketId,
|
||||
&$failureResult,
|
||||
): ScanAttempt {
|
||||
$ticket = $this->baseQuery()
|
||||
->where('tenant_code', $scanner->tenant_codigo)
|
||||
@@ -212,31 +205,32 @@ class ScannerTicketService
|
||||
$ticketId = (int) $ticket->getKey();
|
||||
|
||||
if (! $this->scannerCanScan($scanner, $ticket)) {
|
||||
$failureResult = ScanAttemptResult::CategoryForbidden;
|
||||
$this->resolveScanAttempt(
|
||||
$scanAttempt,
|
||||
ScanAttemptResult::CategoryForbidden,
|
||||
$ticketId,
|
||||
);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'ticket' => __('api.ticket.scanner_category_forbidden'),
|
||||
]);
|
||||
return $scanAttempt->refresh()->setRelation('ticket', $ticket);
|
||||
}
|
||||
|
||||
if ($ticket->is_used) {
|
||||
$failureResult = ScanAttemptResult::AlreadyScanned;
|
||||
$this->resolveScanAttempt(
|
||||
$scanAttempt,
|
||||
ScanAttemptResult::AlreadyScanned,
|
||||
$ticketId,
|
||||
);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'ticket' => __('api.ticket.already_scanned'),
|
||||
]);
|
||||
return $scanAttempt->refresh()->setRelation('ticket', $ticket);
|
||||
}
|
||||
|
||||
if (! $ticket->is_valid) {
|
||||
$failureResult = $ticket->is_expired
|
||||
$result = $ticket->is_expired
|
||||
? ScanAttemptResult::Expired
|
||||
: ScanAttemptResult::NotValid;
|
||||
$this->resolveScanAttempt($scanAttempt, $result, $ticketId);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'ticket' => $ticket->is_expired
|
||||
? __('api.ticket.expired_for_scan')
|
||||
: __('api.ticket.not_valid_for_scan'),
|
||||
]);
|
||||
return $scanAttempt->refresh()->setRelation('ticket', $ticket);
|
||||
}
|
||||
|
||||
$ticket->forceFill([
|
||||
@@ -254,14 +248,15 @@ class ScannerTicketService
|
||||
|
||||
return $scanAttempt->refresh()->setRelation('ticket', $ticket);
|
||||
});
|
||||
} catch (ModelNotFoundException) {
|
||||
$this->resolveScanAttempt($scanAttempt, ScanAttemptResult::TicketNotFound);
|
||||
|
||||
return $scanAttempt->refresh();
|
||||
} catch (Throwable $exception) {
|
||||
$result = $exception instanceof ModelNotFoundException
|
||||
? ScanAttemptResult::TicketNotFound
|
||||
: ($failureResult ?? ScanAttemptResult::UnexpectedError);
|
||||
report($exception);
|
||||
$this->resolveScanAttempt($scanAttempt, ScanAttemptResult::UnexpectedError, $ticketId);
|
||||
|
||||
$this->resolveScanAttempt($scanAttempt, $result, $ticketId);
|
||||
|
||||
throw $exception;
|
||||
return $scanAttempt->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -291,19 +291,19 @@ class ScannerTicketControllerTest extends TestCase
|
||||
$this->assertNotNull($scanAttempt->resolved_at);
|
||||
}
|
||||
|
||||
public function test_scan_requires_a_uuid_in_the_data_field(): void
|
||||
public function test_scan_returns_an_attempt_for_invalid_qr_data(): void
|
||||
{
|
||||
Sanctum::actingAs($this->scanner);
|
||||
|
||||
$this->postJson('/api/v1/scanner/tickets/scan')
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('message', 'El QR proporcionado es inválido.')
|
||||
->assertJsonPath('errors.data.0', 'El QR proporcionado es inválido.');
|
||||
->assertOk()
|
||||
->assertJsonPath('data.scan_attempt.id', fn (mixed $id): bool => is_int($id))
|
||||
->assertJsonPath('data.scan_attempt.result', ScanAttemptResult::InvalidQr->value);
|
||||
|
||||
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => 'not-a-uuid'])
|
||||
->assertUnprocessable()
|
||||
->assertJsonPath('message', 'El QR proporcionado es inválido.')
|
||||
->assertJsonPath('errors.data.0', 'El QR proporcionado es inválido.');
|
||||
->assertOk()
|
||||
->assertJsonPath('data.scan_attempt.id', fn (mixed $id): bool => is_int($id))
|
||||
->assertJsonPath('data.scan_attempt.result', ScanAttemptResult::InvalidQr->value);
|
||||
|
||||
$scanAttempts = ScanAttempt::query()->orderBy('id')->get();
|
||||
$this->assertCount(2, $scanAttempts);
|
||||
@@ -330,8 +330,9 @@ class ScannerTicketControllerTest extends TestCase
|
||||
Sanctum::actingAs($otherScanner);
|
||||
|
||||
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('ticket');
|
||||
->assertOk()
|
||||
->assertJsonPath('data.scan_attempt.id', fn (mixed $id): bool => is_int($id))
|
||||
->assertJsonPath('data.scan_attempt.result', ScanAttemptResult::AlreadyScanned->value);
|
||||
$this->assertSame($this->scanner->id, $ticket->fresh()->scanner_user_id);
|
||||
$this->assertDatabaseHas('scan_attempts', [
|
||||
'scanner_user_id' => $otherScanner->id,
|
||||
@@ -354,8 +355,9 @@ class ScannerTicketControllerTest extends TestCase
|
||||
Sanctum::actingAs($this->scanner);
|
||||
|
||||
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $ticket->ticket])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('ticket');
|
||||
->assertOk()
|
||||
->assertJsonPath('data.scan_attempt.id', fn (mixed $id): bool => is_int($id))
|
||||
->assertJsonPath('data.scan_attempt.result', ScanAttemptResult::CategoryForbidden->value);
|
||||
$this->assertNull($ticket->fresh()->used_at);
|
||||
$this->assertDatabaseHas('scan_attempts', [
|
||||
'scanner_user_id' => $this->scanner->id,
|
||||
@@ -407,7 +409,9 @@ class ScannerTicketControllerTest extends TestCase
|
||||
$this->getJson("/api/v1/scanner/tickets/{$foreignTicket->ticket}")
|
||||
->assertNotFound();
|
||||
$this->postJson('/api/v1/scanner/tickets/scan', ['data' => $foreignTicket->ticket])
|
||||
->assertNotFound();
|
||||
->assertOk()
|
||||
->assertJsonPath('data.scan_attempt.id', fn (mixed $id): bool => is_int($id))
|
||||
->assertJsonPath('data.scan_attempt.result', ScanAttemptResult::TicketNotFound->value);
|
||||
$this->assertDatabaseHas('scan_attempts', [
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
'scanner_user_id' => $this->scanner->id,
|
||||
|
||||
Reference in New Issue
Block a user