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.

121 lines
6.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\Course;
use App\Models\CourseSign;
use App\Models\User;
use App\Services\SchoolmateEnrollmentService;
use Illuminate\Console\Command;
class BackfillSchoolmate extends Command
{
protected $signature = 'schoolmate:backfill
{course_ids : 课程ID,多个用英文逗号分隔}
{--execute : 正式补齐;不传时只检查并输出遗漏名单}';
protected $description = '按指定课程检查或补齐审核通过学员的校友资格';
public function handle(SchoolmateEnrollmentService $service)
{
$raw = (string) $this->argument('course_ids');
if (!preg_match('/^[1-9][0-9]*(,[1-9][0-9]*)*$/D', $raw)) {
$this->error('课程ID必须为正整数,多个用英文逗号分隔。');
return 1;
}
$ids = array_values(array_unique(explode(',', $raw)));
$courses = Course::whereIn('id', $ids);
$found = (clone $courses)->get(['id', 'name', 'start_date']);
$missingIds = array_diff($ids, $found->pluck('id')->all());
if ($missingIds) {
$this->error('课程不存在或已删除:' . implode(',', $missingIds));
return 1;
}
// 缺少开课日期时无法确定历史入库时间,整批停止,避免只补一部分。
foreach ($found as $course) {
if (!$course->start_date) {
$this->error("课程 {$course->id}【{$course->name}】缺少开课日期,请先补全。");
return 1;
}
}
$this->table(['课程ID', '课程名称', '开课日期'], $found->map(function ($course) {
return [$course->id, $course->name, $course->start_date];
})->all());
$execute = (bool) $this->option('execute');
$this->info($execute ? '正式执行:仅补齐尚非校友的审核通过学员。' : '检查模式:不会修改任何学员数据。');
$total = 0;
$updated = 0;
$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, $courseInfo[$user->id] ?? '', $user->source_start_date];
if ($execute) {
$updated += $service->promote($user->id, $user->source_start_date);
}
}
$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();
}
}