校友遗漏

master
lion 3 days ago
parent d378d6899c
commit 64480a3112

@ -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();
}
}

@ -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());
}
}

Loading…
Cancel
Save