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

master
lion 3 months ago
commit 7c78d41da1

@ -0,0 +1,89 @@
<?php
namespace App\Console\Commands;
use App\Models\Company;
use App\Models\User;
use Illuminate\Console\Command;
class SyncCompany extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sync:company';
/**
* The console command description.
*
* @var string
*/
protected $description = '全量同步公司信息(每天凌晨执行,不同步经纬度和地址)';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// 全量同步所有有公司名称的用户
$this->syncAllCompanies();
return $this->info('全量同步完成');
}
/**
* 全量同步公司信息
*/
public function syncAllCompanies()
{
// 获取所有有公司名称的用户全量同步不限制company_id
$users = User::whereNotNull('company_name')
->orderBy('id', 'desc')
->get();
$total = $users->count();
if ($total == 0) {
return $this->info('没有需要同步的用户');
}
$this->info("开始全量同步公司信息,共 {$total} 个用户");
$bar = $this->output->createProgressBar($total);
$bar->start();
$successCount = 0;
$failCount = 0;
foreach ($users as $user) {
// 调用模型方法同步公司信息(不包含经纬度和地址)
$result = Company::syncCompanyFromUser($user);
if ($result['success']) {
$successCount++;
$bar->setMessage($result['company']->company_name . ' 同步成功', 'status');
} else {
$failCount++;
$bar->setMessage($user->company_name . ' ' . $result['message'], 'status');
}
$bar->advance();
}
$bar->finish();
$this->newLine();
$this->info("同步完成:成功 {$successCount} 个,失败 {$failCount} 个");
return $this->info('公司信息-全量同步完成');
}
}

@ -32,6 +32,8 @@ class Kernel extends ConsoleKernel
$schedule->command('auto_schoolmate')->everyThirtyMinutes();
// 更新公司信息
$schedule->command('update_company')->everyTenMinutes();
// 全量同步公司信息(每天凌晨执行,不同步经纬度和地址)
$schedule->command('sync:company')->dailyAt('02:00');
// 同步老师课程方向
$schedule->command('sync:teacher_direction')->hourly();
}

@ -407,7 +407,11 @@ class OtherController extends CommonController
// 区域明细统计
$areas = CourseSign::area($start_date, $end_date, 1, $courses->pluck('id'), true);
return $this->success(compact('list', 'courseTypesSum', 'areas'));
// 获取统计项元数据
$statistics_metadata = Course::getStatisticsMetadata();
return $this->success(compact('list', 'courseTypesSum', 'areas', 'statistics_metadata'));
}
/**
@ -449,8 +453,79 @@ class OtherController extends CommonController
switch ($export_type) {
case 'course_signs_invested':
// 被投企业明细 - 使用与coursesHome相同的算法
$companies = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $course_ids, true);
// 确保 $course_ids 是数组格式
$courseIdsArray = $course_ids ? (is_array($course_ids) ? $course_ids : $course_ids->toArray()) : null;
$companies = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $courseIdsArray, true);
foreach ($companies as $company) {
// 获取该公司的学员信息
// 使用与yhInvestedTotal相同的筛选逻辑status=1筛选course_ids和日期范围
// 直接通过公司ID查询学员
$userIds = User::where('company_id', $company->id)->pluck('id')->toArray();
if (empty($userIds)) {
// 如果没有学员,设置空值
$userNamesStr = '';
$courseNamesStr = '';
$courseTypesStr = '';
$totalCourseCount = 0;
} else {
$userCourseSigns = CourseSign::getStudentList(CourseType::START_DATE, $end_date, 1, $courseIdsArray)
->whereIn('user_id', $userIds)
->with(['user', 'course.typeDetail'])
->get();
// 按学员分组
$usersData = [];
foreach ($userCourseSigns as $sign) {
if (!$sign->user) {
continue;
}
$userId = $sign->user_id;
if (!isset($usersData[$userId])) {
$usersData[$userId] = [
'user' => $sign->user,
'courseSigns' => [],
];
}
$usersData[$userId]['courseSigns'][] = $sign;
}
// 收集所有学员的姓名、课程名称、课程体系
$userNames = [];
$allCourseNames = [];
$allCourseTypes = [];
$totalCourseCount = 0;
foreach ($usersData as $userData) {
$user = $userData['user'];
$courseSigns = collect($userData['courseSigns']);
if ($courseSigns->isNotEmpty()) {
$userNames[] = $user->name ?? '';
// 收集课程名称
$courseNames = $courseSigns->pluck('course.name')->filter()->unique()->values()->toArray();
$allCourseNames = array_merge($allCourseNames, $courseNames);
// 收集课程体系
$courseTypes = $courseSigns->pluck('course.typeDetail.name')
->filter()
->unique()
->values()
->toArray();
$allCourseTypes = array_merge($allCourseTypes, $courseTypes);
// 累计报名课程数
$totalCourseCount += $courseSigns->count();
}
}
// 去重并合并
$userNamesStr = implode('、', array_filter(array_unique($userNames)));
$courseNamesStr = implode('、', array_filter(array_unique($allCourseNames)));
$courseTypesStr = implode('、', array_filter(array_unique($allCourseTypes)));
}
$data[] = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
@ -461,6 +536,10 @@ class OtherController extends CommonController
'contact_mail' => $company->contact_mail ?? '',
'company_tag' => $company->company_tag ?? '',
'credit_code' => ' ' . $company->credit_code ?? '',
'user_names' => $userNamesStr,
'course_names' => $courseNamesStr,
'course_types' => $courseTypesStr,
'course_count' => $totalCourseCount,
];
}
$fields = [
@ -473,6 +552,10 @@ class OtherController extends CommonController
'contact_mail' => '联系邮箱',
'company_tag' => '企业资质',
'credit_code' => '统一社会信用代码',
'user_names' => '学员姓名',
'course_names' => '课程名称',
'course_types' => '课程体系',
'course_count' => '报名课程数',
];
$filename = '被投企业明细';
break;
@ -1398,9 +1481,33 @@ class OtherController extends CommonController
case 'cover_head_total':
// 苏州头部企业明细 - 使用模型方法
$companies = CourseSign::toubuqiye($start_date, $end_date, $course_ids, true);
// 确保 $course_ids 是数组格式
$courseIdsArray = $course_ids ? (is_array($course_ids) ? $course_ids : $course_ids->toArray()) : null;
$companies = CourseSign::toubuqiye($start_date, $end_date, $courseIdsArray, true);
// 预先获取所有符合条件的课程报名记录与toubuqiye方法逻辑一致
$courseSignsQuery = CourseSign::getStudentList($start_date, $end_date, 1, $courseIdsArray);
$allCourseSigns = $courseSignsQuery->with(['user', 'course.typeDetail'])->get();
// 按公司ID分组课程报名记录
$companyCourseSigns = [];
foreach ($allCourseSigns as $sign) {
if (!$sign->user || !$sign->user->company) {
continue;
}
$companyId = $sign->user->company->id;
if (!isset($companyCourseSigns[$companyId])) {
$companyCourseSigns[$companyId] = [];
}
$companyCourseSigns[$companyId][] = $sign;
}
foreach ($companies as $company) {
$data[] = [
// 获取该公司的学员信息(只包含有课程报名的学员)
$userCourseSigns = collect($companyCourseSigns[$company->id] ?? []);
// 公司基本信息(只在第一行使用)
$companyInfo = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
@ -1412,6 +1519,85 @@ class OtherController extends CommonController
'contact_phone' => $company->contact_phone ?? '',
'contact_mail' => $company->contact_mail ?? '',
];
if ($userCourseSigns->isEmpty()) {
// 如果没有学员,仍然导出公司基本信息
$data[] = array_merge($companyInfo, [
'user_name' => '',
'course_names' => '',
'course_types' => '',
'course_count' => 0,
]);
} else {
// 按学员分组
$usersData = [];
foreach ($userCourseSigns as $sign) {
if (!$sign->user) {
continue;
}
$userId = $sign->user_id;
if (!isset($usersData[$userId])) {
$usersData[$userId] = [
'user' => $sign->user,
'courseSigns' => [],
];
}
$usersData[$userId]['courseSigns'][] = $sign;
}
// 每个学员一行
$isFirstRow = true;
foreach ($usersData as $userData) {
$user = $userData['user'];
$courseSigns = collect($userData['courseSigns']);
if ($courseSigns->isEmpty()) {
continue;
}
// 获取课程名称列表,用中文顿号分隔
$courseNames = $courseSigns->pluck('course.name')->filter()->unique()->values()->implode('、');
// 获取课程体系列表,用中文顿号分隔
$courseTypes = $courseSigns->pluck('course.typeDetail.name')
->filter()
->unique()
->values()
->implode('、');
// 报名课程数
$courseCount = $courseSigns->count();
if ($isFirstRow) {
// 第一行:显示公司信息
$data[] = array_merge($companyInfo, [
'user_name' => $user->name ?? '',
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
]);
$isFirstRow = false;
} else {
// 后续行:公司信息为空
$data[] = [
'company_name' => '',
'company_legal_representative' => '',
'company_date' => '',
'company_address' => '',
'company_city' => '',
'company_area' => '',
'company_tag' => '',
'business_scope' => '',
'contact_phone' => '',
'contact_mail' => '',
'user_name' => $user->name ?? '',
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
];
}
}
}
}
$fields = [
'company_name' => '企业名称',
@ -1424,16 +1610,68 @@ class OtherController extends CommonController
'business_scope' => '营业范围',
'contact_phone' => '联系电话',
'contact_mail' => '联系邮箱',
'user_name' => '学员姓名',
'course_names' => '课程名称',
'course_types' => '课程体系',
'course_count' => '报名课程数',
];
$filename = '苏州头部企业明细';
break;
case 'cover_rencai_total':
// 高层次人才明细 - 使用模型方法
$users = CourseSign::rencai($start_date, $end_date, $course_ids, true);
// 确保 $course_ids 是数组格式
$courseIdsArray = $course_ids ? (is_array($course_ids) ? $course_ids : $course_ids->toArray()) : null;
$users = CourseSign::rencai($start_date, $end_date, $courseIdsArray, true);
// 加载关联关系
$users->load('company');
// 获取所有用户的课程报名记录使用与rencai相同的筛选条件
$userIds = $users->pluck('id')->toArray();
$userCourseSigns = CourseSign::getStudentList($start_date, $end_date, 1, $courseIdsArray)
->whereIn('user_id', $userIds)
->with(['user', 'course.typeDetail'])
->get();
// 按用户ID分组课程报名记录
$userCourseSignsMap = [];
foreach ($userCourseSigns as $sign) {
if (!$sign->user) {
continue;
}
$userId = $sign->user_id;
if (!isset($userCourseSignsMap[$userId])) {
$userCourseSignsMap[$userId] = [];
}
$userCourseSignsMap[$userId][] = $sign;
}
foreach ($users as $user) {
// 获取该用户的课程报名记录
$courseSigns = collect($userCourseSignsMap[$user->id] ?? []);
if ($courseSigns->isEmpty()) {
// 如果没有课程报名记录,设置空值
$courseNamesStr = '';
$courseTypesStr = '';
$totalCourseCount = 0;
} else {
// 收集课程名称
$courseNames = $courseSigns->pluck('course.name')->filter()->unique()->values()->toArray();
$courseNamesStr = implode('、', array_filter($courseNames));
// 收集课程体系
$courseTypes = $courseSigns->pluck('course.typeDetail.name')
->filter()
->unique()
->values()
->toArray();
$courseTypesStr = implode('、', array_filter($courseTypes));
// 统计报名课程数
$totalCourseCount = $courseSigns->count();
}
$data[] = [
'user_name' => $user->name ?? '',
'mobile' => $user->mobile ?? '',
@ -1442,6 +1680,9 @@ class OtherController extends CommonController
'company_city' => $user->company->company_city ?? '',
'company_position' => $user->company_position ?? '',
'education' => $user->education ?? '',
'course_names' => $courseNamesStr,
'course_types' => $courseTypesStr,
'course_count' => $totalCourseCount,
];
}
$fields = [
@ -1452,6 +1693,9 @@ class OtherController extends CommonController
'company_city' => '所在城市',
'company_position' => '职位',
'education' => '学历',
'course_names' => '课程名称',
'course_types' => '课程体系',
'course_count' => '报名课程数',
];
$filename = '高层次人才明细';
break;
@ -1461,7 +1705,10 @@ class OtherController extends CommonController
$companiesData = CourseSign::shangshi($start_date, $end_date, $course_ids, true);
foreach ($companiesData as $item) {
$company = $item['company'];
$data[] = [
$users = $item['users'] ?? [];
// 公司基本信息(只在第一行使用)
$companyInfo = [
'company_name' => $company->company_name,
'company_legal_representative' => $company->company_legal_representative ?? '',
'company_date' => $company->company_date ?? '',
@ -1469,10 +1716,74 @@ class OtherController extends CommonController
'company_address' => $company->company_address ?? '',
'company_city' => $company->company_city ?? '',
'company_area' => $company->company_area ?? '',
'company_tag' => $company->company_tag ?? '',
'business_scope' => $company->business_scope ?? '',
'contact_phone' => $company->contact_phone ?? '',
'contact_mail' => $company->contact_mail ?? '',
];
if (empty($users)) {
// 如果没有学员,仍然导出公司基本信息
$data[] = array_merge($companyInfo, [
'user_name' => '',
'course_names' => '',
'course_types' => '',
'course_count' => 0,
]);
} else {
// 每个学员一行
$isFirstRow = true;
foreach ($users as $userInfo) {
$user = $userInfo['user'] ?? null;
$courses = $userInfo['courses'] ?? [];
if (empty($courses)) {
// 如果没有课程,仍然显示学员信息
$courseNames = '';
$courseTypes = '';
$courseCount = 0;
} else {
// 收集课程名称
$courseNames = collect($courses)->pluck('course_name')->filter()->unique()->values()->implode('、');
// 收集课程体系
$courseTypes = collect($courses)->pluck('course_type')->filter()->unique()->values()->implode('、');
// 报名课程数
$courseCount = count($courses);
}
if ($isFirstRow) {
// 第一行:显示公司信息
$data[] = array_merge($companyInfo, [
'user_name' => $userInfo['user_name'] ?? ($user->name ?? ''),
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
]);
$isFirstRow = false;
} else {
// 后续行:公司信息为空
$data[] = [
'company_name' => '',
'company_legal_representative' => '',
'company_date' => '',
'stock_date' => '',
'company_address' => '',
'company_city' => '',
'company_area' => '',
'company_tag' => '',
'business_scope' => '',
'contact_phone' => '',
'contact_mail' => '',
'user_name' => $userInfo['user_name'] ?? ($user->name ?? ''),
'course_names' => $courseNames,
'course_types' => $courseTypes,
'course_count' => $courseCount,
];
}
}
}
}
$fields = [
'company_name' => '企业名称',
@ -1482,9 +1793,14 @@ class OtherController extends CommonController
'company_address' => '地址',
'company_city' => '所在城市',
'company_area' => '所在区域',
'company_tag' => '企业资质',
'business_scope' => '营业范围',
'contact_phone' => '联系电话',
'contact_mail' => '联系邮箱',
'user_name' => '学员姓名',
'course_names' => '课程名称',
'course_types' => '课程体系',
'course_count' => '报名课程数',
];
$filename = '重点上市公司明细';
break;

@ -57,79 +57,79 @@ class UserController extends BaseController
$all = request()->all();
$list = $this->model->with(underlineToHump($all['show_relation'] ?? []))
->with([
'courseSigns' => function ($query) use ($all) {
$query->where('status', 1)->with('course.teacher', 'course.typeDetail');
}
])->where(function ($query) use ($all) {
if (isset($all['keyword'])) {
$query->whereHas('courses', function ($q) use ($all) {
$q->where('name', 'like', '%' . $all['keyword'] . '%');
})->orWhere('name', 'like', '%' . $all['keyword'] . '%');
}
if (isset($all['has_course']) && $all['has_course'] == 1) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
});
}
if (isset($all['filter']) && !empty($all['filter'])) {
foreach ($all['filter'] as $condition) {
$key = $condition['key'] ?? null;
$op = $condition['op'] ?? null;
$value = $condition['value'] ?? null;
if (!isset($key) || !isset($op) || !isset($value)) {
'courseSigns' => function ($query) use ($all) {
$query->where('status', 1)->with('course.teacher', 'course.typeDetail');
}
])->where(function ($query) use ($all) {
if (isset($all['keyword'])) {
$query->whereHas('courses', function ($q) use ($all) {
$q->where('name', 'like', '%' . $all['keyword'] . '%');
})->orWhere('name', 'like', '%' . $all['keyword'] . '%');
}
if (isset($all['has_course']) && $all['has_course'] == 1) {
$query->whereHas('courseSigns', function ($q) {
$q->where('status', 1);
});
}
if (isset($all['filter']) && !empty($all['filter'])) {
foreach ($all['filter'] as $condition) {
$key = $condition['key'] ?? null;
$op = $condition['op'] ?? null;
$value = $condition['value'] ?? null;
if (!isset($key) || !isset($op) || !isset($value)) {
continue;
}
// 等于
if ($op == 'eq') {
$query->where($key, $value);
}
// 不等于
if ($op == 'neq') {
$query->where($key, '!=', $value);
}
// 大于
if ($op == 'gt') {
$query->where($key, '>', $value);
}
// 大于等于
if ($op == 'egt') {
$query->where($key, '>=', $value);
}
// 小于
if ($op == 'lt') {
$query->where($key, '<', $value);
}
// 小于等于
if ($op == 'elt') {
$query->where($key, '<=', $value);
}
// 模糊搜索
if ($op == 'like') {
$query->where($key, 'like', '%' . $value . '%');
}
// 否定模糊搜索
if ($op == 'notlike') {
$query->where($key, 'not like', '%' . $value . '%');
}
// null搜索
if ($op == 'null') {
$query->whereNull($key);
}
// notnull搜索
if ($op == 'notnull') {
$query->whereNotNull($key);
}
// 范围搜索
if ($op == 'range') {
list($from, $to) = explode(',', $value);
if (empty($from) || empty($to)) {
continue;
}
// 等于
if ($op == 'eq') {
$query->where($key, $value);
}
// 不等于
if ($op == 'neq') {
$query->where($key, '!=', $value);
}
// 大于
if ($op == 'gt') {
$query->where($key, '>', $value);
}
// 大于等于
if ($op == 'egt') {
$query->where($key, '>=', $value);
}
// 小于
if ($op == 'lt') {
$query->where($key, '<', $value);
}
// 小于等于
if ($op == 'elt') {
$query->where($key, '<=', $value);
}
// 模糊搜索
if ($op == 'like') {
$query->where($key, 'like', '%' . $value . '%');
}
// 否定模糊搜索
if ($op == 'notlike') {
$query->where($key, 'not like', '%' . $value . '%');
}
// null搜索
if ($op == 'null') {
$query->whereNull($key);
}
// notnull搜索
if ($op == 'notnull') {
$query->whereNotNull($key);
}
// 范围搜索
if ($op == 'range') {
list($from, $to) = explode(',', $value);
if (empty($from) || empty($to)) {
continue;
}
$query->whereBetween($key, [$from, $to]);
}
$query->whereBetween($key, [$from, $to]);
}
}
})->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc');
}
})->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc');
if (isset($all['is_export']) && !empty($all['is_export'])) {
$list = $list->get()->toArray();
$export_fields = $all['export_fields'] ?? [];
@ -168,25 +168,26 @@ class UserController extends BaseController
* @OA\Parameter(name="courses_end_date", in="query", @OA\Schema(type="string"), required=false, description="课程结束时间"),
* @OA\Parameter(name="is_vip", in="query", @OA\Schema(type="string"), required=false, description="是否vip0否1是"),
* @OA\Parameter(name="courses_ing", in="query", @OA\Schema(type="string"), required=false, description="是否课程进行中0否1是"),
* @OA\Parameter(name="is_schoolmate", in="query", @OA\Schema(type="string"), required=false, description="is_schoolmate"),
* @OA\Parameter(name="mobile", in="query", @OA\Schema(type="string"), required=true, description="mobile"),
* @OA\Parameter(name="status", in="query", @OA\Schema(type="string"), required=true, description="审核状态"),
* @OA\Parameter(name="course_type", in="query", @OA\Schema(type="string"), required=true, description="course_type"),
* @OA\Parameter(name="company_has_share", in="query", @OA\Schema(type="string"), required=true, description="是否有股份"),
* @OA\Parameter(name="keyword", in="query", @OA\Schema(type="string"), required=true, description="关键词"),
* @OA\Parameter(name="start_company_date", in="query", @OA\Schema(type="string"), required=true, description="开始成立日期"),
* @OA\Parameter(name="end_company_date", in="query", @OA\Schema(type="string"), required=true, description="结束成立日期"),
* @OA\Parameter(name="start_birthday", in="query", @OA\Schema(type="string"), required=true, description="开始出生日期"),
* @OA\Parameter(name="end_birthday", in="query", @OA\Schema(type="string"), required=true, description="结束出生日期"),
* @OA\Parameter(name="is_schoolmate", in="query", @OA\Schema(type="string"), required=false, description="是否校友0否1是"),
* @OA\Parameter(name="mobile", in="query", @OA\Schema(type="string"), required=false, description="手机号"),
* @OA\Parameter(name="status", in="query", @OA\Schema(type="string"), required=false, description="审核状态0待审核1通过2不通过"),
* @OA\Parameter(name="course_type", in="query", @OA\Schema(type="string"), required=false, description="课程体系ID"),
* @OA\Parameter(name="company_has_share", in="query", @OA\Schema(type="string"), required=false, description="是否有股份0否1是"),
* @OA\Parameter(name="keyword", in="query", @OA\Schema(type="string"), required=false, description="关键词(搜索学校、专业、介绍)"),
* @OA\Parameter(name="start_company_date", in="query", @OA\Schema(type="string"), required=false, description="开始成立日期"),
* @OA\Parameter(name="end_company_date", in="query", @OA\Schema(type="string"), required=false, description="结束成立日期"),
* @OA\Parameter(name="start_birthday", in="query", @OA\Schema(type="string"), required=false, description="开始出生日期"),
* @OA\Parameter(name="end_birthday", in="query", @OA\Schema(type="string"), required=false, description="结束出生日期"),
* @OA\Parameter(name="sign_start_date", in="query", @OA\Schema(type="string"), required=false, description="报名开始时间"),
* @OA\Parameter(name="sign_end_date", in="query", @OA\Schema(type="string"), required=false, description="报名结束时间"),
* @OA\Parameter(name="company_need_fund", in="query", @OA\Schema(type="string"), required=true, description="是否需要融资"),
* @OA\Parameter(name="is_fee", in="query", @OA\Schema(type="string"), required=true, description="是否缴费0否1是"),
* @OA\Parameter(name="has_openid", in="query", @OA\Schema(type="string"), required=true, description="是否绑定小程序0否1是"),
* @OA\Parameter(name="year", in="query", @OA\Schema(type="string"), required=true, description="年份"),
* @OA\Parameter(name="is_black", in="query", @OA\Schema(type="string"), required=true, description="是否黑名单0否1是"),
* @OA\Parameter(name="is_yh_invested", in="query", @OA\Schema(type="string"), required=true, description="是否元和已投企业0否1是"),
* @OA\Parameter(name="company_tag", in="query", @OA\Schema(type="string"), required=true, description="企业标签"),
* @OA\Parameter(name="company_need_fund", in="query", @OA\Schema(type="string"), required=false, description="是否需要融资0否1是"),
* @OA\Parameter(name="is_fee", in="query", @OA\Schema(type="string"), required=false, description="是否缴费0否1是"),
* @OA\Parameter(name="has_openid", in="query", @OA\Schema(type="string"), required=false, description="是否绑定小程序0否1是"),
* @OA\Parameter(name="year", in="query", @OA\Schema(type="string"), required=false, description="年份,默认为当前年份"),
* @OA\Parameter(name="is_black", in="query", @OA\Schema(type="string"), required=false, description="是否黑名单0否1是"),
* @OA\Parameter(name="is_yh_invested", in="query", @OA\Schema(type="string"), required=false, description="是否元和已投企业0否1是"),
* @OA\Parameter(name="is_company_market", in="query", @OA\Schema(type="string"), required=false, description="是否上市公司0否1是"),
* @OA\Parameter(name="company_tag", in="query", @OA\Schema(type="string"), required=false, description="企业标签"),
* @OA\Parameter(name="talent_tags", in="query", @OA\Schema(type="string"), required=false, description="人才标签,多个英文逗号分隔"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
@ -210,10 +211,10 @@ class UserController extends BaseController
'company'
)
->with([
'courseSigns' => function ($query) {
$query->with('course.typeDetail')->orderBy('fee_status', 'desc');
}
])->withCount([
'courseSigns' => function ($query) {
$query->with('course.typeDetail')->orderBy('fee_status', 'desc');
}
])->withCount([
'appointments' => function ($query) {
$query->whereIn('status', [0, 1]);
}
@ -228,6 +229,16 @@ class UserController extends BaseController
}
});
}
// 是否上市公司
if (isset($all['is_company_market'])) {
$list = $list->whereHas('company', function ($query) use ($all) {
if ($all['is_company_market'] == 1) {
$query->where('company_market', 1);
} else {
$query->where('company_market', 0);
}
});
}
// company_tag等价于company_type新旧数据兼容
if (isset($all['company_type'])) {
$all['company_tag'] = $all['company_type'];

@ -176,11 +176,15 @@ class Company extends SoftDeletesModel
}
/**
* 根据用户信息更新公司信息
* 根据用户信息更新/同步公司信息(统一方法)
* @param User $user 用户对象
* @param bool $skipIfHasCompany 如果已有公司关联company_id > 0是否跳过默认true
* @param bool $updateAddress 是否更新地址默认true
* @param bool $updateLocation 是否更新经纬度默认true
* @param bool $setCompanyIdOnFail 失败时是否设置company_id=0默认true
* @return array 返回结果 ['success' => bool, 'message' => string, 'company' => Company|null]
*/
public static function updateCompanyFromUser($user)
public static function updateCompanyFromUser($user, $skipIfHasCompany = true, $updateAddress = true, $updateLocation = true, $setCompanyIdOnFail = true)
{
if (!$user || empty($user->company_name)) {
return ['success' => false, 'message' => '用户或公司名称为空', 'company' => null];
@ -188,7 +192,7 @@ class Company extends SoftDeletesModel
// 如果已经有有效的公司关联company_id > 0跳过
// 允许处理 company_id = -1待更新或 null初始状态的情况
if ($user->company_id && $user->company_id > 0) {
if ($skipIfHasCompany && $user->company_id && $user->company_id > 0) {
return ['success' => false, 'message' => '用户已有公司关联', 'company' => null];
}
@ -213,27 +217,32 @@ class Company extends SoftDeletesModel
if (!$result) {
// 标识一下未匹配到公司,后续可以根据这个字段筛选出未匹配到公司的用户
$user->company_id = 0;
$user->save();
if ($setCompanyIdOnFail) {
$user->company_id = 0;
$user->save();
}
return ['success' => false, 'message' => '公司不存在', 'company' => null];
}
// 如果$result['enterpriseName']存在数字,跳过
if (preg_match('/\d/', $result['enterpriseName'])) {
$user->company_id = 0;
$user->save();
if ($setCompanyIdOnFail) {
$user->company_id = 0;
$user->save();
}
return ['success' => false, 'message' => '公司名称包含数字,跳过', 'company' => null];
}
if ($result['status'] == '未注册') {
$user->company_id = 0;
$user->save();
if ($setCompanyIdOnFail) {
$user->company_id = 0;
$user->save();
}
return ['success' => false, 'message' => '公司未注册,跳过', 'company' => null];
}
$where = ['company_name' => $result['enterpriseName']];
$data = [
'company_address' => $result['address'],
'business_scope' => $result['businessScope'],
'company_city' => $result['city'],
'contact_mail' => $result['contactMail'],
@ -266,6 +275,11 @@ class Company extends SoftDeletesModel
'partners' => $result['partners'] ?? null,
];
// 根据参数决定是否更新地址
if ($updateAddress) {
$data['company_address'] = $result['address'];
}
$company = Company::updateOrCreate($where, $data);
// 更新用户关联
@ -275,12 +289,25 @@ class Company extends SoftDeletesModel
// 更新上市状态
self::updateMarketStatus($company->id);
// 更新位置
self::updateLocation($company->id);
// 根据参数决定是否更新位置(经纬度)
if ($updateLocation) {
self::updateLocation($company->id);
}
return ['success' => true, 'message' => '更新成功', 'company' => $company];
}
/**
* 全量同步公司信息(不包含地址和经纬度)
* @param User $user 用户对象
* @return array 返回结果 ['success' => bool, 'message' => string, 'company' => Company|null]
*/
public static function syncCompanyFromUser($user)
{
// 调用统一方法参数设置为不跳过已有公司、不更新地址、不更新经纬度、失败时不设置company_id
return self::updateCompanyFromUser($user, false, false, false, false);
}
/**
* 更新经纬度信息
* @param int $companyId 公司ID

@ -10,8 +10,18 @@ use Illuminate\Filesystem\Filesystem;
class Course extends SoftDeletesModel
{
protected $appends = [
'date_status', 'publicize', 'sign_date_status', 'qrcode', 'teacher_detail',
'status_text', 'is_fee_text', 'is_arrange_text', 'show_txl_text', 'show_mobile_text', 'auto_schoolmate_text', 'is_virtual_text'
'date_status',
'publicize',
'sign_date_status',
'qrcode',
'teacher_detail',
'status_text',
'is_fee_text',
'is_arrange_text',
'show_txl_text',
'show_mobile_text',
'auto_schoolmate_text',
'is_virtual_text'
];
protected $casts = ['publicize_ids' => 'json'];
@ -72,7 +82,8 @@ class Course extends SoftDeletesModel
public function getPublicizeAttribute($value)
{
if (empty($this->publicize_ids)) return [];
if (empty($this->publicize_ids))
return [];
return Upload::whereIn('id', $this->publicize_ids)->get();
}
@ -285,5 +296,101 @@ class Course extends SoftDeletesModel
return $url;
}
/**
* 获取课程统计项元数据
* 返回每个统计项的计算逻辑和验证方法说明
* @return array
*/
public static function getStatisticsMetadata()
{
return [
'course_signs_total' => [
'name' => '报名人数',
'from' => '统计在指定时间范围内、指定课程范围内的所有报名记录数量。包括所有状态的报名(待审核、已通过、已拒绝等),同时会加上历史课程数据中的人数统计。一个学员报名多个课程会计算多次。',
'verify' => ''
],
'course_signs_pass' => [
'name' => '审核通过人数',
'from' => '统计在指定时间范围内、指定课程范围内审核通过的报名记录数量。只统计状态为"已通过"的报名,不包括待审核、已拒绝等其他状态。同时会加上历史课程数据中已通过的人数统计。一个学员报名多个课程会计算多次。',
'verify' => ''
],
'course_signs_pass_unique' => [
'name' => '审核通过人数去重',
'from' => '统计在指定时间范围内、指定课程范围内审核通过的学员人数(按学员去重)。与"审核通过人数"的区别是:如果同一个学员报名了多个课程,这里只计算一次。通过学员的手机号进行去重,确保每个学员只统计一次。同时会加上历史课程数据中去重后的人数统计。',
'verify' => ''
],
'course_total' => [
'name' => '开课场次',
'from' => '统计在指定时间范围内、指定课程体系范围内的开课场次数。一个课程可能有多个场次(比如分多天上课),这里统计的是场次数,不是课程数。只要场次的开始时间或结束时间在指定时间范围内,就会被统计。',
'verify' => ''
],
'course_day_total' => [
'name' => '开课天数',
'from' => '统计在指定时间范围内、指定课程体系范围内的开课天数总和。只统计标记为"需要统计天数"的场次每个场次可能有不同的天数比如3天、5天等将所有场次的天数相加得到总天数。',
'verify' => ''
],
'company_market_total' => [
'name' => '上市公司数',
'from' => '统计在指定时间范围内、指定课程范围内报名的学员中,所在公司为上市公司的公司数量。通过学员关联到其所在公司,筛选出已上市的公司,然后按公司去重统计。如果同一公司有多个学员报名,只计算一次。',
'verify' => ''
],
'ganbu_total' => [
'name' => '跟班学员数',
'from' => '统计在指定时间范围内、指定课程范围内的跟班学员人数。筛选条件1)学员的"来源"字段包含"跟班学员"2)课程类型标记为"需要统计跟班学员"。同时会加上员工参与表中的额外数据。按学员去重,同一学员只计算一次。',
'verify' => ''
],
'company_market_year_total' => [
'name' => '今年上市公司数量',
'from' => '统计所有在今年(当前年份)上市的公司数量。直接从上市公司表中查询,统计上市日期在今年内的公司。这个统计不依赖学员报名记录,统计的是所有在今年上市的公司,不管是否有学员报名。',
'verify' => ''
],
'company_market_after_enrollment_total' => [
'name' => '入学后上市公司数量',
'from' => '统计所有标记为"入学后上市"的公司数量。直接从上市公司表中查询,统计标记为"入学后上市"的公司。这个统计不依赖学员报名记录,统计的是所有在学员入学后才上市的公司。',
'verify' => ''
],
'course_signs_invested' => [
'name' => '累计被投企业数',
'from' => '统计从课程开始日期到结束日期,在指定课程范围内报名的学员中,所在公司为被投企业的公司数量。通过学员关联到其所在公司,筛选出标记为"被投企业"的公司,检查公司的被投时间是否在截止日期之前(或没有记录被投时间),然后按公司去重统计。同一公司多个学员只计算一次。',
'verify' => ''
],
'company_invested_after_enrollment_total' => [
'name' => '入学后被投企业数量',
'from' => '统计在指定时间范围内报名的学员中,所在公司在学员入学后被投的公司数量。以学员报名的课程开课时间作为"入学时间",筛选出公司的被投时间晚于入学时间的公司,然后按公司去重统计。同一公司多个学员只计算一次。',
'verify' => ''
],
'company_invested_year_total' => [
'name' => '今年被投企业数',
'from' => '统计在指定时间范围内报名的学员中,所在公司在指定年份范围内被投的公司数量。从开始日期和结束日期中提取年份范围,筛选出公司的被投时间在指定年份范围内的公司,然后按公司去重统计。同一公司多个学员只计算一次。',
'verify' => ''
],
'company_join_total' => [
'name' => '元和员工参与人数',
'from' => '统计在指定时间范围内、指定课程范围内报名的学员中,所在公司为元和投资公司的学员人数。通过学员关联到其所在公司,筛选出元和投资的公司,然后按学员去重统计。同一学员报名多个课程只计算一次。',
'verify' => ''
],
'company_ganbu_total' => [
'name' => '全市干部参与企业',
'from' => '统计在指定时间范围内、指定课程范围内报名的学员中,来源为"跟班学员"且课程类型标记为"需要统计跟班学员"的学员人数。与"跟班学员数"的统计逻辑相同,同时会加上员工参与表中的额外数据。按学员去重,同一学员只计算一次。',
'verify' => ''
],
'cover_head_total' => [
'name' => '苏州头部企业',
'from' => '统计在指定时间范围内、指定课程范围内报名的学员中,所在公司为苏州头部企业的公司数量。通过学员关联到其所在公司,筛选出标记为"苏州头部企业"的公司,然后按公司去重统计。同一公司多个学员只计算一次。',
'verify' => ''
],
'cover_rencai_total' => [
'name' => '高层次人才',
'from' => '统计在指定时间范围内、指定课程范围内报名的学员中,标记为"高层次人才"的学员人数。通过学员的个人信息(如学历等)判断是否为高层次人才,然后按学员去重统计。同一学员报名多个课程只计算一次。',
'verify' => ''
],
'cover_stock_total' => [
'name' => '重点上市公司',
'from' => '统计在指定时间范围内、指定课程范围内报名的学员中,所在公司为重点上市公司的公司数量。通过学员关联到其所在公司,筛选出重点上市公司,然后按公司去重统计。与"上市公司数"的统计逻辑相同,但筛选的是重点上市公司。同一公司多个学员只计算一次。',
'verify' => ''
],
];
}
}

@ -299,15 +299,15 @@ class CourseSign extends SoftDeletesModel
$years = [];
if ($start_date && $end_date) {
// 从开始和结束日期中提取年份范围
$startYear = (int)date('Y', strtotime($start_date));
$endYear = (int)date('Y', strtotime($end_date));
$startYear = (int) date('Y', strtotime($start_date));
$endYear = (int) date('Y', strtotime($end_date));
// 生成所有年份的数组
for ($year = $startYear; $year <= $endYear; $year++) {
$years[] = $year;
}
} else {
// 如果没有提供日期,使用当前年份
$years[] = (int)date('Y');
$years[] = (int) date('Y');
}
// 获取这些公司中标记为被投的公司
@ -323,7 +323,7 @@ class CourseSign extends SoftDeletesModel
foreach ($projectUsers as $item) {
$investDate = $item['investDate'] ?? null;
if ($investDate) {
$investYear = (int)date('Y', strtotime($investDate));
$investYear = (int) date('Y', strtotime($investDate));
if (in_array($investYear, $years)) {
$hasInvestInYears = true;
break;
@ -431,18 +431,30 @@ class CourseSign extends SoftDeletesModel
public static function companyMarketAfterEnrollment($start_date, $end_date, $course_ids = null, $retList = false)
{
$courseSignsQuery = self::getStudentList($start_date, $end_date, null, $course_ids);
$courseSignsForStock = $courseSignsQuery->with('user.company')->get();
$courseSignsForStock = $courseSignsQuery->with(['user.company', 'course'])->get();
$companiesAfterEnrollment = [];
foreach ($courseSignsForStock as $sign) {
if ($sign->user && $sign->user->company && $sign->user->company->company_market == 1) {
$signDate = \Carbon\Carbon::parse($sign->created_at)->format('Y-m-d');
// 入学时间使用参与课程的课程开始时间course->start_date不使用报名时间created_at
$enrollmentDate = null;
if ($sign->course && $sign->course->start_date) {
$enrollmentDate = \Carbon\Carbon::parse($sign->course->start_date)->format('Y-m-d');
}
// 如果没有课程开始时间,跳过这条记录
if (!$enrollmentDate) {
continue;
}
$stockDate = $sign->user->company->stock_date;
if ($stockDate && $stockDate >= $signDate) {
// 上市时间 >= 课程开始时间(入学时间),说明是入学后上市
if ($stockDate && $stockDate >= $enrollmentDate) {
$companyId = $sign->user->company->id;
if (!isset($companiesAfterEnrollment[$companyId])) {
$companiesAfterEnrollment[$companyId] = [
'company' => $sign->user->company,
'first_sign_date' => $signDate,
'first_enrollment_date' => $enrollmentDate, // 首次入学时间(课程开始时间)
'stock_date' => $stockDate,
'users' => [],
];
}
@ -475,18 +487,18 @@ class CourseSign extends SoftDeletesModel
$companiesAfterEnrollment = [];
foreach ($courseSignsForInvest as $sign) {
if ($sign->user && $sign->user->company && $sign->user->company->is_yh_invested == 1) {
// 使用课程开课时间作为入学时间
// 入学时间使用参与课程的课程开始时间course->start_date不使用报名时间created_at
$enrollmentDate = null;
if ($sign->course && $sign->course->start_date) {
$enrollmentDate = \Carbon\Carbon::parse($sign->course->start_date)->format('Y-m-d');
}
// 如果没有课时间,跳过这条记录
// 如果没有课程开始时间,跳过这条记录
if (!$enrollmentDate) {
continue;
}
// 从 project_users 中检查是否有任何一个被投时间 >= 课程开课时间
// 从 project_users 中检查是否有任何一个被投时间 >= 课程开始时间(入学时间)
$projectUsers = $sign->user->company->project_users;
$hasInvestAfterEnrollment = false;
$investDate = null;
@ -494,7 +506,7 @@ class CourseSign extends SoftDeletesModel
foreach ($projectUsers as $projectUser) {
if (!empty($projectUser['investDate'])) {
$currentInvestDate = $projectUser['investDate'];
// 只要有一个被投时间 >= 课程开课时间,就满足条件
// 只要有一个被投时间 >= 课程开始时间(入学时间),就满足条件
if ($currentInvestDate >= $enrollmentDate) {
$hasInvestAfterEnrollment = true;
// 记录满足条件的被投时间(如果有多个,记录最早的)
@ -505,13 +517,13 @@ class CourseSign extends SoftDeletesModel
}
}
}
// 只要有一个被投时间 >= 课程开课时间,说明是入学后被投
// 只要有一个被投时间 >= 课程开始时间(入学时间),说明是入学后被投
if ($hasInvestAfterEnrollment && $investDate) {
$companyId = $sign->user->company->id;
if (!isset($companiesAfterEnrollment[$companyId])) {
$companiesAfterEnrollment[$companyId] = [
'company' => $sign->user->company,
'first_sign_date' => $enrollmentDate,
'first_enrollment_date' => $enrollmentDate, // 首次入学时间(课程开始时间)
'invest_date' => $investDate,
'users' => [],
];

@ -190,12 +190,27 @@ EOF
return 0
}
# 首先尝试使用默认密码
# 执行 push
echo ""
echo "=========================================="
echo "执行 git push"
echo "=========================================="
echo ""
# 使用局部变量保存当前使用的密码
local current_password="$PASSWORD"
# 首先尝试使用默认密码 push
echo "执行: git push $selected_remote $current_branch (使用默认密码)"
if do_git_push "$PASSWORD" "$selected_remote" "$current_branch"; then
if do_git_push "$current_password" "$selected_remote" "$current_branch"; then
echo ""
echo "=========================================="
echo "执行结果"
echo "=========================================="
echo "✅ Push: 成功"
echo ""
echo "✅ 仓库 $repo_path 提交完成!"
echo "=========================================="
else
# 默认密码失败,提示用户输入密码
echo ""
@ -207,11 +222,23 @@ EOF
echo "使用您输入的密码重新执行: git push $selected_remote $current_branch"
if do_git_push "$user_password" "$selected_remote" "$current_branch"; then
echo ""
echo "=========================================="
echo "执行结果"
echo "=========================================="
echo "✅ Push: 成功"
echo ""
echo "✅ 仓库 $repo_path 提交完成!"
echo "=========================================="
else
echo ""
echo "=========================================="
echo "执行结果"
echo "=========================================="
echo "❌ Push: 失败"
echo ""
echo "❌ 仓库 $repo_path push 失败,请检查密码和网络连接"
echo "=========================================="
fi
fi

Loading…
Cancel
Save