diff --git a/app/Console/Commands/BackfillSchoolmate.php b/app/Console/Commands/BackfillSchoolmate.php index cdae480..30ed4b1 100644 --- a/app/Console/Commands/BackfillSchoolmate.php +++ b/app/Console/Commands/BackfillSchoolmate.php @@ -3,6 +3,8 @@ namespace App\Console\Commands; use App\Models\Course; +use App\Models\CourseSign; +use App\Models\User; use App\Services\SchoolmateEnrollmentService; use Illuminate\Console\Command; @@ -43,18 +45,76 @@ class BackfillSchoolmate extends Command $this->info($execute ? '正式执行:仅补齐尚非校友的审核通过学员。' : '检查模式:不会修改任何学员数据。'); $total = 0; $updated = 0; - $service->pendingUsers($courses)->chunkById(200, function ($users) use ($service, $execute, &$total, &$updated) { + $service->pendingUsers($courses)->chunkById(200, function ($users) use ($service, $execute, $ids, &$total, &$updated) { $rows = []; + $courseInfo = $this->approvedCourseInfo($users->pluck('id')->all(), $ids); foreach ($users as $user) { $total++; - $rows[] = [$user->id, $user->name, $user->mobile, $user->source_start_date]; + $rows[] = [$user->id, $user->name, $user->mobile, $courseInfo[$user->id] ?? '', $user->source_start_date]; if ($execute) { $updated += $service->promote($user->id, $user->source_start_date); } } - $this->table(['学员ID', '姓名', '手机号', '拟入库日期(所选课程最早开课日)'], $rows); + $this->table(['学员ID', '姓名', '手机号', '所选课程中审核通过的课程(ID/名称/开课日期)', '拟入库日期(所选课程最早开课日)'], $rows); }, 'users.id', 'id'); $this->info("发现遗漏 {$total} 人" . ($execute ? ",实际补齐 {$updated} 人。" : ';正式补齐请追加 --execute。')); + $this->info('反向核对:当前是校友,但所选课程中没有未删除的审核通过记录。'); + $this->line('此名单仅供核对,可能通过其他课程成为校友,不会取消校友资格。'); + $outsideTotal = 0; + User::where('is_schoolmate', 1) + ->whereDoesntHave('courseSigns', function ($query) use ($ids) { + $query->whereIn('course_id', $ids)->where('status', 1); + }) + ->chunkById(200, function ($users) use ($ids, &$outsideTotal) { + $selectedInfo = $this->selectedEnrollmentInfo($users->pluck('id')->all(), $ids); + $courseInfo = $this->approvedCourseInfo($users->pluck('id')->all()); + $rows = []; + foreach ($users as $user) { + $outsideTotal++; + $rows[] = [$user->id, $user->name, $user->mobile, $user->schoolmate_time, + $selectedInfo[$user->id] ?? '无有效报名记录', + $courseInfo[$user->id] ?? '无有效审核通过课程记录']; + } + $this->table(['学员ID', '姓名', '手机号', '入库时间', '所选课程报名情况(ID/名称/状态)', '其他审核通过课程(仅供核对,非入库来源证明)'], $rows); + }); + $this->info("所选课程无审核通过记录的校友共 {$outsideTotal} 人。"); return 0; } + + private function selectedEnrollmentInfo(array $userIds, array $courseIds): array + { + return CourseSign::query() + ->join('courses', 'courses.id', '=', 'course_signs.course_id') + ->whereNull('courses.deleted_at') + ->whereIn('course_signs.user_id', $userIds) + ->whereIn('course_signs.course_id', $courseIds) + ->orderBy('courses.id')->orderBy('course_signs.id') + ->get(['course_signs.user_id', 'course_signs.status', 'courses.id as course_id', 'courses.name']) + ->groupBy('user_id')->map(function ($signs) { + return $signs->map(function ($sign) { + $status = CourseSign::$intToString['status'][$sign->status] ?? ('未知状态:' . $sign->status); + return "{$sign->course_id}/{$sign->name}/{$status}"; + })->unique()->implode("\n"); + })->all(); + } + + private function approvedCourseInfo(array $userIds, ?array $courseIds = null): array + { + $query = CourseSign::query() + ->join('courses', 'courses.id', '=', 'course_signs.course_id') + ->whereNull('courses.deleted_at') + ->whereIn('course_signs.user_id', $userIds) + ->where('course_signs.status', 1); + if ($courseIds !== null) { + $query->whereIn('course_signs.course_id', $courseIds); + } + + return $query->orderBy('courses.start_date')->orderBy('courses.id') + ->get(['course_signs.user_id', 'courses.id as course_id', 'courses.name', 'courses.start_date']) + ->groupBy('user_id')->map(function ($signs) { + return $signs->unique('course_id')->map(function ($sign) { + return "{$sign->course_id}/{$sign->name}/{$sign->start_date}"; + })->implode("\n"); + })->all(); + } } diff --git a/tests/Feature/SchoolmateEnrollmentCommandTest.php b/tests/Feature/SchoolmateEnrollmentCommandTest.php index bae7e75..77b29ed 100644 --- a/tests/Feature/SchoolmateEnrollmentCommandTest.php +++ b/tests/Feature/SchoolmateEnrollmentCommandTest.php @@ -118,4 +118,39 @@ class SchoolmateEnrollmentCommandTest extends TestCase } $this->assertEquals(0, DB::table('users')->where('id', 1)->value('is_schoolmate')); } + public function test_report_includes_matching_courses_and_schoolmates_without_approved_selected_enrollment(): void + { + $this->course(1, '2024-01-01'); + $this->course(2, '2025-02-01'); + DB::table('courses')->where('id', 1)->update(['name' => '指定课程甲']); + DB::table('courses')->where('id', 2)->update(['name' => '其他课程乙']); + foreach ([1, 2, 3, 4, 5, 6, 7] as $id) { + DB::table('users')->insert(['id' => $id, 'name' => '学员' . $id, + 'is_schoolmate' => $id === 1 ? 0 : 1, + 'deleted_at' => $id === 6 ? '2025-01-01 00:00:00' : null]); + } + $this->sign(1, 1); + $this->sign(2, 2); // 其他课程校友应列入反向名单 + $this->sign(3, 1, 0); // 仅待审核也需列出 + $this->sign(4, 1, 4); // 仅已取消也需列出 + $this->sign(5, 1, 1, '2025-01-01 00:00:00'); // 已删除报名不算有效记录 + $this->sign(7, 1, 0); + $this->sign(7, 1, 1); // 同时有通过记录,应排除 + $exit = \Illuminate\Support\Facades\Artisan::call('schoolmate:backfill', ['course_ids' => '1']); + $output = \Illuminate\Support\Facades\Artisan::output(); + $this->assertSame(0, $exit); + $this->assertStringContainsString('1/指定课程甲/2024-01-01', $output); + $this->assertStringContainsString('2/其他课程乙/2025-02-01', $output); + $this->assertStringContainsString('所选课程无审核通过记录的校友共 4 人', $output); + $this->assertStringContainsString('学员2', $output); + $this->assertStringContainsString('学员5', $output); + $this->assertStringContainsString('学员3', $output); + $this->assertStringContainsString('学员4', $output); + $this->assertStringContainsString('1/指定课程甲/待审核', $output); + $this->assertStringContainsString('1/指定课程甲/已取消', $output); + foreach ([6, 7] as $id) $this->assertStringNotContainsString('学员' . $id, $output); + $this->assertEquals(0, DB::table('users')->where('id', 1)->value('is_schoolmate')); + $this->assertEquals(6, DB::table('users')->where('is_schoolmate', 1)->count()); + } + }