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.

111 lines
3.9 KiB

<?php
namespace App\Services;
use App\Models\AudienceRegistration;
use App\Models\AudienceSession;
use App\Models\Competition;
use App\Models\User;
use App\Services\Sms\AudienceRegistrationSmsNotifier;
use App\Support\BizDateTime;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class AudienceRegistrationService
{
public function __construct(private readonly AudienceRegistrationSmsNotifier $smsNotifier)
{
}
/**
* @param array{audience_session_id: int, name: string, company?: string|null, job?: string|null, purpose: string} $data
*/
public function register(User $user, Competition $competition, array $data): AudienceRegistration
{
$registration = DB::transaction(function () use ($user, $competition, $data) {
/** @var AudienceSession|null $session */
$session = AudienceSession::query()
->where('competition_id', $competition->id)
->whereKey($data['audience_session_id'])
->lockForUpdate()
->first();
if ($session === null) {
throw ValidationException::withMessages([
'audience_session_id' => ['场次不存在'],
]);
}
if (! $session->enabled) {
throw ValidationException::withMessages([
'audience_session_id' => ['该场次暂未开放报名'],
]);
}
$now = now()->timezone(BizDateTime::TIMEZONE);
$status = $session->windowStatus($now);
if ($status === AudienceSession::STATUS_NOT_STARTED) {
throw ValidationException::withMessages([
'audience_session_id' => ['该场次尚未开始报名'],
]);
}
if ($status === AudienceSession::STATUS_EXPIRED) {
throw ValidationException::withMessages([
'audience_session_id' => ['该场次报名已截止'],
]);
}
$already = AudienceRegistration::query()
->where('audience_session_id', $session->id)
->where('user_id', $user->id)
->exists();
if ($already) {
throw ValidationException::withMessages([
'audience_session_id' => ['您已报名该场次'],
]);
}
$count = AudienceRegistration::query()
->where('audience_session_id', $session->id)
->count();
if ($count >= (int) $session->capacity) {
throw ValidationException::withMessages([
'audience_session_id' => ['该场次已满额'],
]);
}
return AudienceRegistration::query()->create([
'competition_id' => $competition->id,
'audience_session_id' => $session->id,
'user_id' => $user->id,
'name' => $data['name'],
'phone' => trim((string) $user->mobile),
'company' => $this->nullableText($data['company'] ?? null),
'job' => $this->nullableText($data['job'] ?? null),
'purpose' => $data['purpose'],
'answers_json' => is_array($data['answers_json'] ?? null) ? $data['answers_json'] : null,
'registered_at' => now(),
]);
});
try {
$this->smsNotifier->notify($registration);
} catch (\Throwable $e) {
Log::error('sms.audience_registration unexpected_error', [
'registration_id' => $registration->id,
'error' => $e->getMessage(),
]);
}
return $registration;
}
private function nullableText(mixed $value): ?string
{
$s = trim((string) ($value ?? ''));
return $s === '' ? null : $s;
}
}