Merge branch 'master' of ssh://47.101.48.251:/data/git/wx.sstbc.com

master
lion 2 months ago
commit b113864512

@ -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} 位已处理学员");
}
}

@ -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 脚本更新公司信息...");

@ -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",

@ -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]),
// 元禾同事数

@ -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']);

@ -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 是否需要额外数据TraineeStudenttrue需要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;
}
}

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

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
// 榜单标签,多个值用英文逗号分隔
$table->string('ranking_tag')->nullable()->comment('榜单标签');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('companies', function (Blueprint $table) {
$table->dropColumn('ranking_tag');
});
}
};
Loading…
Cancel
Save