joinSub($this->eligibleSources($courses), 'schoolmate_sources', function ($join) { $join->on('users.id', '=', 'schoolmate_sources.user_id'); }) ->whereRaw('COALESCE(users.is_schoolmate, 0) != 1') ->select('users.*', 'schoolmate_sources.source_start_date'); } private function eligibleSources($courses) { return CourseSign::query() ->join('courses', 'courses.id', '=', 'course_signs.course_id') ->whereNull('courses.deleted_at') ->whereIn('courses.id', (clone $courses)->select('courses.id')) ->where('course_signs.status', 1) ->select('course_signs.user_id') ->selectRaw('MIN(courses.start_date) as source_start_date') ->groupBy('course_signs.user_id'); } public function ineligibleSchoolmates($courses) { return User::query() ->where('users.is_schoolmate', 1) ->whereNotIn('users.id', $this->eligibleSources($courses) ->select('course_signs.user_id')->whereNotNull('course_signs.user_id')); } /** * 锁定用户后重新核验,避免按批次查询时取得的旧资格更新用户。 * 返回 1 表示入库,-1 表示取消,0 表示无需变更。 */ public function reconcile(int $userId, $courses): int { return DB::transaction(function () use ($userId, $courses) { $user = User::whereKey($userId)->lockForUpdate()->first(); if (!$user) { return 0; } $source = $this->eligibleSources($courses) ->where('course_signs.user_id', $userId)->first(); if ($source && $user->is_schoolmate != 1) { return User::batchUpdateToSchoolmate([$userId], $source->source_start_date); } if (!$source && $user->is_schoolmate == 1) { $user->is_schoolmate = 0; $user->schoolmate_time = null; return $user->save() ? -1 : 0; } return 0; }); } public function automaticCourses() { // 保留开课前一天入库的原行为,同时补查历史及开课后审核通过的学员。 return Course::where('auto_schoolmate', 1) ->whereNotNull('start_date') ->whereDate('start_date', '<=', now()->addDay()->toDateString()); } public function promote(int $userId, string $startDate): int { return DB::transaction(function () use ($userId, $startDate) { // 手工补齐和自动任务同时运行时,不覆盖已有校友的入库时间。 $user = User::whereKey($userId)->lockForUpdate()->first(); if (!$user || $user->is_schoolmate == 1) { return 0; } return User::batchUpdateToSchoolmate([$userId], $startDate); }); } }