diff --git a/app/Console/Commands/AutoSchoolmate.php b/app/Console/Commands/AutoSchoolmate.php index 2015621..c08343a 100755 --- a/app/Console/Commands/AutoSchoolmate.php +++ b/app/Console/Commands/AutoSchoolmate.php @@ -41,38 +41,67 @@ class AutoSchoolmate extends Command */ public function handle() { - // 获取所有已开始且需要自动加入校友库的课程 + // 获取所有已开始且需要自动加入校友库的课程(只处理存在 start_date 的课程) $today = date('Y-m-d'); $courses = Course::where('auto_schoolmate', 1) - ->where(function ($query) use ($today) { - // 方式1: start_date 已填写且 <= 今天 - $query->where(function ($q) use ($today) { - $q->whereNotNull('start_date') - ->where('start_date', '<=', $today); - }) - // 方式2: 或者课程状态为进行中(即使 start_date 未及时填写) - ->orWhere('course_status', 10); - }) + ->whereNotNull('start_date') + ->where('start_date', '<=', $today) ->get(); + if ($courses->isEmpty()) { + return $this->info('没有需要处理的课程'); + } + + $this->info("找到 {$courses->count()} 个需要处理的课程"); + $totalUpdated = 0; + $totalSkipped = 0; + $processedUserIds = []; // 记录已处理的用户ID,避免重复处理 + foreach ($courses as $course) { - // 获取报名通过的学员 - $courseSigns = CourseSign::where('course_id', $course->id)->where('status', 1)->get(); + // 获取报名通过的学员,并排除已经是校友的用户 + $courseSigns = CourseSign::where('course_id', $course->id) + ->where('status', 1) + ->whereHas('user', function ($query) { + // 排除已经是校友的用户(is_schoolmate = 1) + $query->whereRaw('COALESCE(is_schoolmate, 0) != 1'); + })->get(); + if ($courseSigns->isEmpty()) { continue; } - // 只更新还不是校友的学员;从 非校友→校友 时顺带写入 schoolmate_time - $userIds = $courseSigns->pluck('user_id')->unique()->values(); - $updated = User::whereIn('id', $userIds) - ->whereRaw('COALESCE(is_schoolmate, 0) != 1') - ->update(['is_schoolmate' => 1, 'schoolmate_time' => now()]); + // 获取需要更新的用户ID,并排除已经在本脚本中处理过的用户 + $userIds = $courseSigns->pluck('user_id') + ->unique() + ->filter(function ($userId) use (&$processedUserIds) { + // 如果已经处理过,跳过 + if (in_array($userId, $processedUserIds)) { + return false; + } + $processedUserIds[] = $userId; + return true; + }) + ->values() + ->toArray(); + + if (empty($userIds)) { + continue; + } + + // 批量更新:只更新还不是校友的学员;使用课程开课时间作为 schoolmate_time + $updated = User::batchUpdateToSchoolmate($userIds, $course->start_date); $totalUpdated += $updated; + $skipped = count($userIds) - $updated; + $totalSkipped += $skipped; + + if ($updated > 0) { + $this->info("课程【{$course->name}】: 更新 {$updated} 位学员,跳过 {$skipped} 位已处理学员"); + } } - return $this->info("更新完成,共处理 {$totalUpdated} 位学员"); + return $this->info("更新完成,共处理 {$totalUpdated} 位学员,跳过 {$totalSkipped} 位已处理学员"); } } diff --git a/app/Console/Commands/UpdateUserFromCourseSign.php b/app/Console/Commands/UpdateUserFromCourseSign.php index 2398bde..acdb8c7 100644 --- a/app/Console/Commands/UpdateUserFromCourseSign.php +++ b/app/Console/Commands/UpdateUserFromCourseSign.php @@ -2,8 +2,11 @@ namespace App\Console\Commands; +use App\Models\Company; +use App\Models\Course; use App\Models\CourseForm; use App\Models\CourseSign; +use App\Models\CourseType; use App\Models\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; @@ -11,45 +14,11 @@ use Illuminate\Support\Facades\Artisan; class UpdateUserFromCourseSign extends Command { /** - * 限定的手机号列表 + * 课程体系优先级(高研班 > 攀峰班 > 初创班) * * @var array */ - protected $allowedMobiles = [ - '18068449126', - '13775025589', - '13372137856', - '15862529818', - '15850007903', - '18662477010', - '13776041671', - '13811803477', - '13816102853', - '18761927103', - '13771899330', - '15850174698', - '13901337246', - '15190599917', - '13601736904', - '18013537727', - '13771990129', - '13862584161', - '18067734553', - '13913088481', - '13382182663', - '15962537089', - '13472506601', - '15366527762', - '17306151525', - '18862122672', - '18768114864', - '13646251927', - '18068036921', - '13962165222', - '13812764284', - '17746389584', - '13813662822', - ]; + protected $courseTypePriority = ['高研班', '攀峰班', '初创班']; /** * The name and signature of the console command. @@ -63,7 +32,7 @@ class UpdateUserFromCourseSign extends Command * * @var string */ - protected $description = '从报名表获取用户填写的信息填充到user数据表(按手机号匹配)'; + protected $description = '从高研班/攀峰班/初创班报名表获取用户填写的信息,按优先级覆盖到user数据表'; /** * Create a new command instance. @@ -82,14 +51,32 @@ class UpdateUserFromCourseSign extends Command */ public function handle() { - // 根据限定手机号列表查找所有用户 - $users = User::whereIn('mobile', $this->allowedMobiles)->get(); + // 获取高研班、攀峰班、初创班三个课程体系下的课程 ID + $courseTypeIds = CourseType::whereIn('name', $this->courseTypePriority)->pluck('id')->toArray(); + if (empty($courseTypeIds)) { + return $this->error('未找到高研班/攀峰班/初创班课程类型'); + } - if ($users->isEmpty()) { - return $this->error('没有找到符合条件的用户'); + $courseIds = Course::whereIn('type', $courseTypeIds)->pluck('id')->toArray(); + if (empty($courseIds)) { + return $this->error('三个课程体系下没有课程'); } - $this->info("找到 {$users->count()} 个用户需要处理"); + // 获取这三个体系下所有有报名数据(data 不为空)的报名记录,再取去重用户 + $userIds = CourseSign::whereIn('course_id', $courseIds) + ->whereNotNull('user_id') + ->whereNotNull('data') + ->pluck('user_id') + ->unique() + ->values() + ->toArray(); + + if (empty($userIds)) { + return $this->error('没有找到符合条件的报名用户'); + } + + $users = User::whereIn('id', $userIds)->get(); + $this->info("找到 {$users->count()} 个用户需要处理(高研班/攀峰班/初创班报名用户)"); $successCount = 0; $failCount = 0; @@ -99,7 +86,7 @@ class UpdateUserFromCourseSign extends Command $this->info("========== 处理用户:{$user->name} (手机号: {$user->mobile}, ID: {$user->id}) =========="); try { - $this->processUser($user); + $this->processUser($user, $courseIds); $successCount++; } catch (\Exception $e) { $this->error("处理用户 {$user->name} 时出错:" . $e->getMessage()); @@ -113,100 +100,130 @@ class UpdateUserFromCourseSign extends Command /** * 处理单个用户 + * 按优先级(高研班 > 攀峰班 > 初创班)选定课程体系,取该体系下最新一条报名数据覆盖用户 + * + * @param \App\Models\User $user + * @param array $courseIds 高研班/攀峰班/初创班下的课程 ID 列表 */ - private function processUser($user) + private function processUser($user, array $courseIds) { $user_id = $user->id; - // 获取该用户的所有报名记录(携带课程及课程类型) + // 仅获取该用户在三个体系下的报名记录(携带课程及课程类型),按 created_at 倒序 $courseSigns = CourseSign::with('course.typeDetail') ->where('user_id', $user_id) + ->whereIn('course_id', $courseIds) ->whereNotNull('data') + ->orderByDesc('created_at') ->get(); if ($courseSigns->isEmpty()) { - $this->info('该用户没有报名记录或报名记录中没有数据'); + $this->info('该用户在此三个体系下没有报名记录或报名记录中没有数据'); return; } - $this->info("找到 {$courseSigns->count()} 条报名记录"); - - // 如果用户报名了多个课程,并且其中包含课程类型是「高研班」或者「攀峰班」的课程 - // 则优先使用这些课程的报名信息更新用户 - $targetCourseSigns = $courseSigns; - if ($courseSigns->count() > 1) { - $specialCourseSigns = $courseSigns->filter(function ($sign) { - $typeName = $sign->course->typeDetail->name ?? ''; - return in_array($typeName, ['高研班', '攀峰班']); + // 统计用户报名的课程体系 + $userCourseTypes = $courseSigns->map(function ($sign) { + return $sign->course->typeDetail->name ?? ''; + })->filter()->unique()->values()->toArray(); + $this->info('用户报名的课程体系:' . implode('、', $userCourseTypes)); + + // 按优先级确定使用的课程体系:有高研班用高研班,否则攀峰班,否则初创班 + $selectedTypeName = null; + foreach ($this->courseTypePriority as $typeName) { + $hasSign = $courseSigns->contains(function ($sign) use ($typeName) { + $name = $sign->course->typeDetail->name ?? ''; + return $name === $typeName; }); - if ($specialCourseSigns->isNotEmpty()) { - $this->info('检测到高研班/攀峰班课程报名记录,将优先使用这些课程的报名信息更新用户'); - $targetCourseSigns = $specialCourseSigns; + if ($hasSign) { + $selectedTypeName = $typeName; + $this->info("检测到用户报名了【{$typeName}】,优先使用该体系的数据"); + break; } } - // 收集所有需要更新的用户字段 + if ($selectedTypeName === null) { + $this->info('未匹配到高研班/攀峰班/初创班课程类型,跳过'); + return; + } + + // 只保留该体系下的报名,且已按 created_at 倒序,取最新一条 + $targetSigns = $courseSigns->filter(function ($sign) use ($selectedTypeName) { + $name = $sign->course->typeDetail->name ?? ''; + return $name === $selectedTypeName; + }); + $latestSign = $targetSigns->first(); + $this->info("使用课程体系【{$selectedTypeName}】下最新报名(course_sign_id: {$latestSign->id})覆盖用户数据"); + + // 从单条最新报名中提取用户字段 $userData = []; $hasCompanyName = false; - foreach ($targetCourseSigns as $courseSign) { - if (empty($courseSign->data) || !is_array($courseSign->data)) { - continue; - } + if (empty($latestSign->data) || !is_array($latestSign->data)) { + $this->info('该条报名 data 为空,无法更新用户'); + return; + } - // 获取该课程的表单字段配置,建立 field -> belong_user_table 的映射关系 - $courseForms = CourseForm::where('course_id', $courseSign->course_id) - ->where('belong_user', 1) // 只获取属于用户信息的字段 - ->whereNotNull('belong_user_table') // 必须有对应的用户表字段 - ->where('belong_user_table', '!=', '') // 用户表字段不能为空 - ->get(['field', 'belong_user_table']); + $courseForms = CourseForm::where('course_id', $latestSign->course_id) + ->where('belong_user', 1) + ->whereNotNull('belong_user_table') + ->where('belong_user_table', '!=', '') + ->get(['field', 'belong_user_table']); - if ($courseForms->isEmpty()) { - continue; - } + if ($courseForms->isEmpty()) { + $this->info('该课程无 belong_user 表单配置,跳过'); + return; + } + + $fieldMapping = []; + foreach ($courseForms as $form) { + $fieldMapping[$form->field] = $form->belong_user_table; + } - // 建立 field -> belong_user_table 的映射关系 - $fieldMapping = []; - foreach ($courseForms as $form) { - $fieldMapping[$form->field] = $form->belong_user_table; + $dataArray = []; + foreach ($latestSign->data as $item) { + if (isset($item['field'], $item['value'])) { + $dataArray[$item['field']] = $item['value']; } + } - // 将 data 数组转换为以 field 为 key 的关联数组 - $dataArray = []; - foreach ($courseSign->data as $item) { - if (isset($item['field']) && isset($item['value'])) { - $dataArray[$item['field']] = $item['value']; + foreach ($fieldMapping as $field => $userTableField) { + if (isset($dataArray[$field]) && $dataArray[$field] !== null && $dataArray[$field] !== '') { + $userData[$userTableField] = $dataArray[$field]; + if ($userTableField === 'company_name') { + $hasCompanyName = true; } } + } + + if (empty($userData)) { + $this->info('未解析出可更新的用户字段'); + return; + } - // 提取属于用户信息的字段,使用 belong_user_table 作为用户表的字段名 - foreach ($fieldMapping as $field => $userTableField) { - if (isset($dataArray[$field]) && $dataArray[$field] !== null && $dataArray[$field] !== '') { - // 如果字段已经在 $userData 中,且新值不为空,则更新(优先使用非空值) - if (!isset($userData[$userTableField]) || empty($userData[$userTableField])) { - $userData[$userTableField] = $dataArray[$field]; - } - - // 检查是否是公司名字 - if ($userTableField === 'company_name') { - $hasCompanyName = true; - } + // 如果用户已有 company_id 且存在对应的 company 数据,则不更新公司名字 + if ($user->company_id) { + $hasCompany = Company::where('id', $user->company_id)->exists(); + if ($hasCompany) { + // 有关联的 company 数据,移除 company_name 字段,不更新 + if (isset($userData['company_name'])) { + unset($userData['company_name']); + $hasCompanyName = false; + $this->info('用户已有关联的 company 数据(company_id: ' . $user->company_id . '),跳过公司名字更新'); } } } if (empty($userData)) { - $this->info('没有找到需要更新的用户信息'); + $this->info('移除公司名字后,没有其他需要更新的用户字段'); return; } $this->info('准备更新以下字段:' . implode(', ', array_keys($userData))); - // 更新用户信息 - // 根据 User::$coverFields 判断是否需要覆盖更新 + // 根据 User::$coverFields 判断覆盖或追加 foreach ($userData as $key => $value) { if (!in_array($key, User::$coverFields)) { - // 追加更新(对于非覆盖字段) $currentValue = $user->$key ?? ''; if (!empty($currentValue)) { $tempArray = explode(',', $currentValue . ',' . $value); @@ -216,10 +233,8 @@ class UpdateUserFromCourseSign extends Command } } - // 检查公司名字是否发生变化 $oldCompanyName = $user->company_name; $newCompanyName = $userData['company_name'] ?? null; - // 如果公司名字从无到有,或者发生变化,都需要更新 $companyNameChanged = $hasCompanyName && isset($newCompanyName) && (empty($oldCompanyName) || $oldCompanyName != $newCompanyName); @@ -228,7 +243,6 @@ class UpdateUserFromCourseSign extends Command $this->info('用户信息更新成功'); - // 如果公司名字发生变化或新增,调用 UpdateCompany 脚本 if ($companyNameChanged) { if (empty($oldCompanyName)) { $this->info("检测到新增公司名字({$newCompanyName}),开始调用 UpdateCompany 脚本更新公司信息..."); diff --git a/app/Http/Controllers/Admin/CompanyController.php b/app/Http/Controllers/Admin/CompanyController.php index 92d0af1..764e7df 100644 --- a/app/Http/Controllers/Admin/CompanyController.php +++ b/app/Http/Controllers/Admin/CompanyController.php @@ -223,6 +223,20 @@ class CompanyController extends BaseController } continue; } + if ($key == 'ranking_tag') { + $valueArray = explode(',', $value); + if (!empty($valueArray)) { + $query->where(function ($q) use ($valueArray) { + foreach ($valueArray as $item) { + $item = trim($item); + if (!empty($item)) { + $q->orWhere('ranking_tag', 'like', '%' . $item . '%'); + } + } + }); + } + continue; + } // 等于 if ($op == 'eq') { @@ -419,6 +433,7 @@ class CompanyController extends BaseController * @OA\Parameter(name="overseas_experience", in="query", @OA\Schema(type="string", nullable=true), description="海外经验"), * @OA\Parameter(name="sales_volume", in="query", @OA\Schema(type="string", nullable=true), description="销售额"), * @OA\Parameter(name="tag", in="query", @OA\Schema(type="string", nullable=true), description="标签,千企走访这类的数据"), + * @OA\Parameter(name="ranking_tag", in="query", @OA\Schema(type="string", nullable=true), description="榜单标签,多个值用英文逗号分隔"), * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), * @OA\Response( * response="200", diff --git a/app/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index 8c49eb8..cb3c471 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -421,7 +421,7 @@ class OtherController extends CommonController 'course_name' => $course->name, 'course_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, [$course->id], false, false), // 跟班学员数量 - 'genban_total' => CourseSign::genban($start_date, $end_date, [$course->id]), + 'genban_total' => CourseSign::genban($start_date, $end_date, [$course->id], false, false), // 被投企业数 'yh_invested_total' => CourseSign::yhInvested($start_date, $end_date, [$course->id]), // 元禾同事数 diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 1efdf1e..12f5dc6 100755 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -263,6 +263,25 @@ class UserController extends BaseController }); } + // 榜单标签查询 + if (isset($all['ranking_tag'])) { + $list = $list->whereHas('company', function ($query) use ($all) { + $string = explode(',', $all['ranking_tag']); + $query->where(function ($q) use ($string) { + foreach ($string as $index => $v) { + $trimmed = trim($v); + if (!empty($trimmed)) { + if ($index === 0) { + $q->where('ranking_tag', 'like', '%' . $trimmed . '%'); + } else { + $q->orWhere('ranking_tag', 'like', '%' . $trimmed . '%'); + } + } + } + }); + }); + } + $list = $list->whereHas('courseSigns', function ($query) use ($all) { if (isset($all['course_id'])) { $query->where('course_id', $all['course_id']); diff --git a/app/Models/CourseSign.php b/app/Models/CourseSign.php index aed9673..463d2dd 100755 --- a/app/Models/CourseSign.php +++ b/app/Models/CourseSign.php @@ -356,9 +356,10 @@ class CourseSign extends SoftDeletesModel * @param string $end_date 结束日期 * @param array|null $course_ids 课程ID数组,不传则统计所有课程 * @param bool $retList 是否返回列表,false返回数量,true返回列表 + * @param bool $needHistory 是否需要额外数据(TraineeStudent),true需要,false不需要 * @return int|\Illuminate\Database\Eloquent\Collection */ - public static function genban($start_date = null, $end_date = null, $course_ids = null, $retList = false) + public static function genban($start_date = null, $end_date = null, $course_ids = null, $retList = false, $needHistory = true) { $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); // 获取需要统计跟班学员的课程 @@ -374,14 +375,17 @@ class CourseSign extends SoftDeletesModel return User::with('company')->whereIn('id', $courseSigns->pluck('user_id'))->get(); } else { $baseCount = User::whereIn('id', $courseSigns->pluck('user_id'))->count(); - // 额外数据:从 TraineeStudent 模型中统计 - $traineeStudentTotal = TraineeStudent::where(function ($query) use ($start_date, $end_date) { - // 开始结束日期的筛选。or查询 - if ($start_date && $end_date) { - $query->whereBetween('start_date', [$start_date, $end_date]) - ->orWhereBetween('end_date', [$start_date, $end_date]); - } - })->sum('total'); + $traineeStudentTotal = 0; + if ($needHistory) { + // 额外数据:从 TraineeStudent 模型中统计 + $traineeStudentTotal = TraineeStudent::where(function ($query) use ($start_date, $end_date) { + // 开始结束日期的筛选。or查询 + if ($start_date && $end_date) { + $query->whereBetween('start_date', [$start_date, $end_date]) + ->orWhereBetween('end_date', [$start_date, $end_date]); + } + })->sum('total'); + } return $baseCount + $traineeStudentTotal; } } diff --git a/app/Models/User.php b/app/Models/User.php index 5c6d362..f0266a4 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -243,4 +243,28 @@ class User extends Authenticatable implements Auditable return $user->appointment_total - $useTotal >= 0 ? $user->appointment_total - $useTotal : 0; } + /** + * 批量将用户更新为校友 + * @param array $userIds 用户ID数组 + * @param string $courseStartDate 课程开课时间(用于设置 schoolmate_time) + * @return int 更新的用户数量 + */ + public static function batchUpdateToSchoolmate(array $userIds, string $courseStartDate) + { + if (empty($userIds)) { + return 0; + } + + // 将课程开课时间转换为 datetime 格式 + $schoolmateTime = Carbon::parse($courseStartDate)->format('Y-m-d H:i:s'); + + // 批量更新:只更新还不是校友的学员;从 非校友→校友 时使用课程开课时间作为 schoolmate_time + return self::whereIn('id', $userIds) + ->whereRaw('COALESCE(is_schoolmate, 0) != 1') + ->update([ + 'is_schoolmate' => 1, + 'schoolmate_time' => $schoolmateTime + ]); + } + } diff --git a/database/migrations/2026_01_26_141146_add_ranking_tag_to_companies_table.php b/database/migrations/2026_01_26_141146_add_ranking_tag_to_companies_table.php new file mode 100644 index 0000000..2f1471a --- /dev/null +++ b/database/migrations/2026_01_26_141146_add_ranking_tag_to_companies_table.php @@ -0,0 +1,32 @@ +string('ranking_tag')->nullable()->comment('榜单标签'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('companies', function (Blueprint $table) { + $table->dropColumn('ranking_tag'); + }); + } +};