You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
4.4 KiB
142 lines
4.4 KiB
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\AdminUser;
|
|
use App\Models\AudienceRegistration;
|
|
use App\Models\Competition;
|
|
use App\Models\CompetitionAdmin;
|
|
use App\Support\BizDateTime;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class AudienceCheckinService
|
|
{
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function checkInByTicket(
|
|
Competition $competition,
|
|
string $ticketCode,
|
|
AdminUser|CompetitionAdmin $admin,
|
|
?int $expectedSessionId = null,
|
|
): array {
|
|
$normalized = $this->normalizeTicketCode($ticketCode);
|
|
if ($normalized === '') {
|
|
throw ValidationException::withMessages([
|
|
'ticket_code' => ['无效入场码'],
|
|
]);
|
|
}
|
|
|
|
return DB::transaction(function () use ($competition, $normalized, $admin, $expectedSessionId) {
|
|
/** @var AudienceRegistration|null $row */
|
|
$row = AudienceRegistration::query()
|
|
->where('competition_id', $competition->id)
|
|
->where('ticket_code', $normalized)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if ($row === null) {
|
|
throw ValidationException::withMessages([
|
|
'ticket_code' => ['无效入场码'],
|
|
]);
|
|
}
|
|
|
|
return $this->applyCheckIn($row, $admin, $expectedSessionId);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function checkInByRegistration(
|
|
Competition $competition,
|
|
int $registrationId,
|
|
AdminUser|CompetitionAdmin $admin,
|
|
): array {
|
|
return DB::transaction(function () use ($competition, $registrationId, $admin) {
|
|
/** @var AudienceRegistration|null $row */
|
|
$row = AudienceRegistration::query()
|
|
->where('competition_id', $competition->id)
|
|
->whereKey($registrationId)
|
|
->lockForUpdate()
|
|
->first();
|
|
|
|
if ($row === null) {
|
|
throw ValidationException::withMessages([
|
|
'id' => ['报名记录不存在'],
|
|
]);
|
|
}
|
|
|
|
return $this->applyCheckIn($row, $admin, null);
|
|
});
|
|
}
|
|
|
|
public function normalizeTicketCode(string $raw): string
|
|
{
|
|
$raw = trim($raw);
|
|
if ($raw === '') {
|
|
return '';
|
|
}
|
|
if (preg_match('#/t/([A-Za-z0-9_-]+)#', $raw, $matches) === 1) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return preg_replace('/\s+/', '', $raw) ?? '';
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function applyCheckIn(AudienceRegistration $row, AdminUser|CompetitionAdmin $admin, ?int $expectedSessionId): array
|
|
{
|
|
$row->load('session');
|
|
$session = $row->session;
|
|
|
|
if ($session === null) {
|
|
throw ValidationException::withMessages([
|
|
'ticket_code' => ['无效入场码'],
|
|
]);
|
|
}
|
|
|
|
if ($expectedSessionId !== null && (int) $row->audience_session_id !== $expectedSessionId) {
|
|
throw ValidationException::withMessages([
|
|
'ticket_code' => ['非本场次入场码'],
|
|
]);
|
|
}
|
|
|
|
if (! $session->enabled) {
|
|
throw ValidationException::withMessages([
|
|
'ticket_code' => ['该场次已停用,暂不可核销'],
|
|
]);
|
|
}
|
|
|
|
$already = $row->checked_in_at !== null;
|
|
if (! $already) {
|
|
$row->checked_in_at = now();
|
|
if ($admin instanceof CompetitionAdmin) {
|
|
$row->checked_in_by_competition_admin_id = $admin->id;
|
|
$row->checked_in_by_admin_id = null;
|
|
} else {
|
|
$row->checked_in_by_admin_id = $admin->id;
|
|
$row->checked_in_by_competition_admin_id = null;
|
|
}
|
|
$row->save();
|
|
}
|
|
|
|
return [
|
|
'already_checked_in' => $already,
|
|
'id' => $row->id,
|
|
'name' => $row->name,
|
|
'phone' => $row->phone,
|
|
'company' => $row->company,
|
|
'job' => $row->job,
|
|
'purpose' => $row->purpose,
|
|
'audience_session_id' => (int) $row->audience_session_id,
|
|
'session_name' => $session->name,
|
|
'event_date_text' => $session->event_date_text,
|
|
'checked_in_at' => BizDateTime::toIso8601($row->checked_in_at),
|
|
];
|
|
}
|
|
}
|