user(); $data = $request->validate([ 'signin_code' => ['required', 'string', 'max:64'], ]); [$day, $courseHint] = $this->resolveCourseCheckinDay($data['signin_code']); if (! $day || ! $day->course) { if ($courseHint === 'not_teach_day') { return $this->fail('今日非该课程开课日,无法签到', 422); } return $this->fail('签到码无效', 422); } $signup = CourseSignup::query() ->where('course_id', $day->course_id) ->where('miniapp_user_id', $user->id) ->first(); if (! $signup) { return $this->fail('您尚未报名该课程,无法签到', 422); } $checkinDate = $day->teach_date->toDateString(); $existing = CourseSignupCheckin::query() ->where('course_signup_id', $signup->id) ->whereDate('checkin_date', $checkinDate) ->whereNotNull('checked_in_at') ->first(); if ($existing) { return $this->ok([ 'type' => 'course', 'already_checked_in' => true, 'checked_in_at' => $existing->checked_in_at?->toIso8601String(), 'checkin_date' => $checkinDate, 'target' => MiniappPresenter::serializeCourseList($day->course, $user), ], '今日已签到'); } $checkin = CourseSignupCheckin::query()->updateOrCreate( [ 'course_signup_id' => $signup->id, 'checkin_date' => $checkinDate, ], ['checked_in_at' => now()] ); return $this->ok([ 'type' => 'course', 'already_checked_in' => false, 'checked_in_at' => $checkin->checked_in_at?->toIso8601String(), 'checkin_date' => $checkinDate, 'target' => MiniappPresenter::serializeCourseList($day->course, $user), ], '签到成功'); } public function activity(Request $request): JsonResponse { /** @var MiniappUser $user */ $user = $request->user(); $data = $request->validate([ 'signin_code' => ['required', 'string', 'max:64'], ]); $code = strtoupper(trim($data['signin_code'])); $activity = Activity::query() ->whereRaw('UPPER(code) = ?', [$code]) ->first(); if (! $activity) { $activity = Activity::query() ->get() ->first(fn (Activity $item) => strtoupper(ActivitySigninCodeSync::activitySigninCode($item)) === $code); } if (! $activity) { return $this->fail('签到码无效', 422); } $activity->load(['activityTypeItem', 'sessions'])->loadCount('signups'); $signups = ActivitySignup::query() ->with('session') ->where('activity_id', $activity->id) ->where('miniapp_user_id', $user->id) ->get(); if ($signups->isEmpty()) { return $this->fail('您尚未报名该活动,无法签到', 422); } $today = now()->toDateString(); $signup = $signups->first( fn (ActivitySignup $item) => $item->session?->starts_at?->toDateString() === $today ) ?? ($signups->count() === 1 ? $signups->first() : null); if (! $signup) { return $this->fail('今日无您报名的场次,无法签到', 422); } if (! $signup->activity_session_id) { return $this->fail('报名记录缺少场次信息', 422); } $existing = ActivitySignupCheckin::query() ->where('activity_signup_id', $signup->id) ->where('activity_session_id', $signup->activity_session_id) ->whereNotNull('checked_in_at') ->first(); $target = MiniappPresenter::serializeActivityList($activity, $user); $target['summary'] = strip_tags((string) $activity->intro_html); if ($existing) { return $this->ok([ 'type' => 'activity', 'already_checked_in' => true, 'checked_in_at' => $existing->checked_in_at?->toIso8601String(), 'target' => $target, 'session' => $signup->session ? MiniappPresenter::serializeActivitySession($signup->session, $user) : null, ], '已签到'); } $checkin = ActivitySignupCheckin::query()->updateOrCreate( [ 'activity_signup_id' => $signup->id, 'activity_session_id' => $signup->activity_session_id, ], ['checked_in_at' => now()] ); return $this->ok([ 'type' => 'activity', 'already_checked_in' => false, 'checked_in_at' => $checkin->checked_in_at?->toIso8601String(), 'target' => $target, 'session' => $signup->session ? MiniappPresenter::serializeActivitySession($signup->session, $user) : null, ], '签到成功'); } /** * @return array{0: ?CourseCheckinDay, 1: ?string} */ protected function resolveCourseCheckinDay(string $signinCode): array { $withCourse = [ 'course' => fn ($q) => $q ->with(['courseSystemItem', 'coverMedia', 'promoMedia']) ->withCount('signups'), ]; $day = CourseCheckinDay::query() ->with($withCourse) ->where('signin_code', trim($signinCode)) ->first(); if ($day) { return [$day, null]; } $course = $this->resolveCourseBySigninCode($signinCode); if (! $course) { return [null, null]; } $today = now()->toDateString(); if (! CourseCheckinDaySync::isTeachDate($course, $today)) { return [null, 'not_teach_day']; } $day = CourseCheckinDay::query() ->with($withCourse) ->where('course_id', $course->id) ->whereDate('teach_date', $today) ->first(); return [$day, null]; } protected function resolveCourseBySigninCode(string $signinCode): ?Course { $code = strtoupper(trim($signinCode)); if ($code === '') { return null; } $course = Course::query()->whereRaw('UPPER(code) = ?', [$code])->first(); if ($course) { return $course; } if (preg_match('/^COURSE-(\d+)$/', $code, $matches)) { return Course::query()->find((int) $matches[1]); } return Course::query() ->get() ->first(fn (Course $item) => strtoupper(CourseCheckinDaySync::courseSigninCode($item)) === $code); } }