diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index bbf5911..0375adb 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,10 +15,13 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { + // 发送消息 $schedule->command("sms:notification")->everyMinute(); + // 更新课程状态 $schedule->command('update_sign_status')->everyMinute(); + // 更新首字母 $schedule->command('update_letter')->everyMinute(); - // $schedule->command('update_appointment')->everyMinute(); + // 更新可预约次数 $schedule->command('update_appointment_total')->dailyAt('1:00'); // 生日检测 $schedule->command('check_birthday')->dailyAt('09:00'); @@ -31,7 +34,7 @@ class Kernel extends ConsoleKernel // 更新课程校友资格 $schedule->command('auto_schoolmate')->everyThirtyMinutes(); // 更新公司信息 - $schedule->command('update_company')->everyTenMinutes(); + $schedule->command('update_company')->everyFiveMinutes(); // 全量同步公司信息(每天凌晨执行,不同步经纬度和地址) $schedule->command('sync:company')->dailyAt('02:00'); // 同步老师课程方向 diff --git a/app/Exports/CommonExport.php b/app/Exports/CommonExport.php index cac9453..8ef7741 100755 --- a/app/Exports/CommonExport.php +++ b/app/Exports/CommonExport.php @@ -51,7 +51,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With const HISTORY_COURSES_SUB_COLUMNS = [ 'hc_course_type' => '课程体系', 'hc_course_name' => '课程名称', - 'hc_signs_pass' => '培养人数未去重', + 'hc_signs_pass' => '培养人次未去重', 'hc_signs_pass_unique' => '培养人数去重', 'hc_course_signs_pass' => '课程培养人数', ]; @@ -292,7 +292,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With $endCol = $this->getColumnLetter($index + count(self::USERS_SUB_COLUMNS) - 1); $sheet->mergeCells("{$startCol}1:{$endCol}1"); $sheet->setCellValue("{$startCol}1", $label); - + // 确保第二行表头正确设置所有子列(包括缺失的列) $subIndex = 0; foreach (self::USERS_SUB_COLUMNS as $subField => $subLabel) { @@ -301,7 +301,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With $sheet->setCellValue("{$col}2", $subLabel); $subIndex++; } - + $index += count(self::USERS_SUB_COLUMNS); } elseif (str_contains($field, 'project_users')) { // 项目经理信息列:合并第一行的多个单元格 @@ -309,7 +309,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With $endCol = $this->getColumnLetter($index + count(self::PROJECT_USERS_SUB_COLUMNS) - 1); $sheet->mergeCells("{$startCol}1:{$endCol}1"); $sheet->setCellValue("{$startCol}1", $label); - + // 确保第二行表头正确设置所有子列 $subIndex = 0; foreach (self::PROJECT_USERS_SUB_COLUMNS as $subField => $subLabel) { @@ -317,7 +317,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With $sheet->setCellValue("{$col}2", $subLabel); $subIndex++; } - + $index += count(self::PROJECT_USERS_SUB_COLUMNS); } elseif (str_contains($field, 'history_courses')) { // 历史课程信息列:合并第一行的多个单元格 @@ -325,7 +325,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With $endCol = $this->getColumnLetter($index + count(self::HISTORY_COURSES_SUB_COLUMNS) - 1); $sheet->mergeCells("{$startCol}1:{$endCol}1"); $sheet->setCellValue("{$startCol}1", $label); - + // 确保第二行表头正确设置所有子列 $subIndex = 0; foreach (self::HISTORY_COURSES_SUB_COLUMNS as $subField => $subLabel) { @@ -333,7 +333,7 @@ class CommonExport implements FromCollection, WithStyles, WithColumnWidths, With $sheet->setCellValue("{$col}2", $subLabel); $subIndex++; } - + $index += count(self::HISTORY_COURSES_SUB_COLUMNS); } else { // 其他列:合并第一行和第二行 diff --git a/app/Http/Controllers/Admin/CalendarsController.php b/app/Http/Controllers/Admin/CalendarsController.php index d9170d0..fb12da8 100644 --- a/app/Http/Controllers/Admin/CalendarsController.php +++ b/app/Http/Controllers/Admin/CalendarsController.php @@ -80,7 +80,7 @@ class CalendarsController extends BaseController if (isset($all['month'])) { $query->where('start_time', 'like', $all['month'] . '%'); } - })->count(); + })->where('is_count_days', 1)->count(); // 本年开课场次 $yearCourseCount = Calendar::where(function ($query) use ($all) { if (isset($all['month'])) { @@ -88,7 +88,7 @@ class CalendarsController extends BaseController $year = date('Y', strtotime($all['month'])); $query->where('start_time', 'like', $year . '%'); } - })->count(); + })->where('is_count_days', 1)->count(); return $this->success(compact('list', 'monthDayCalendar', 'yearDayCalendar', 'monthCourseCount', 'yearCourseCount')); } @@ -142,7 +142,7 @@ class CalendarsController extends BaseController * @OA\Parameter(name="is_publish", in="query", @OA\Schema(type="string"), required=true, description="是否向用户发布0否1是"), * @OA\Parameter(name="address", in="query", @OA\Schema(type="string"), required=true, description="地址"), * @OA\Parameter(name="days", in="query", @OA\Schema(type="string"), required=true, description="天数"), - * @OA\Parameter(name="history_courses", in="query", @OA\Schema(type="array", @OA\Items(type="object")), required=false, description="历史课程数组,每项包含:type(课程体系ID), course_name(课程名称), course_type_signs_pass(培养人数未去重), course_type_signs_pass_unique(培养人数去重), course_signs_pass(课程培养人数), start_time(开始时间), end_time(结束时间)"), + * @OA\Parameter(name="history_courses", in="query", @OA\Schema(type="array", @OA\Items(type="object")), required=false, description="历史课程数组,每项包含:type(课程体系ID), course_name(课程名称), course_type_signs_pass(培养人次未去重), course_type_signs_pass_unique(培养人数去重), course_signs_pass(课程培养人数), start_time(开始时间), end_time(结束时间)"), * @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/EmployeeParticipationController.php b/app/Http/Controllers/Admin/EmployeeParticipationController.php index 5630223..2206346 100644 --- a/app/Http/Controllers/Admin/EmployeeParticipationController.php +++ b/app/Http/Controllers/Admin/EmployeeParticipationController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin; use App\Exports\BaseExport; use App\Helpers\ResponseCode; +use App\Models\Course; use App\Models\CustomForm; use App\Models\CustomFormField; use App\Models\EmployeeParticipation; @@ -157,6 +158,7 @@ class EmployeeParticipationController extends BaseController * @OA\Parameter(name="end_date", in="query", @OA\Schema(type="string", format="date"), required=false, description="结束日期"), * @OA\Parameter(name="total", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="数量"), * @OA\Parameter(name="course_type_id", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="课程类型ID"), + * @OA\Parameter(name="course_id", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="课程ID"), * @OA\Parameter(name="course_name", in="query", @OA\Schema(type="string", nullable=true), description="课程名称"), * @OA\Parameter(name="company_name", in="query", @OA\Schema(type="string", nullable=true), description="公司名字"), * @OA\Parameter(name="name", in="query", @OA\Schema(type="string", nullable=true), description="姓名"), @@ -244,27 +246,58 @@ class EmployeeParticipationController extends BaseController $tempFile = $file->getRealPath(); $dataArray = (new FastExcel)->import($tempFile)->toArray(); - // 固定的字段映射(Excel表头中文 => 数据库字段名) - $fieldMapping = [ - '类型' => 'type', - '开始日期' => 'start_date', - '结束日期' => 'end_date', - '数量' => 'total', - '课程类型ID' => 'course_type_id', - '课程名称' => 'course_name', - '公司名字' => 'company_name', - '姓名' => 'name', - '部门' => 'department', + // 类型映射:中文 => 数字 + $typeMapping = [ + '元禾员工参与' => 1, + '干部培训' => 2, + ]; + + // 类型名称映射:数字 => 中文 + $typeNameMapping = [ + 1 => '元禾员工参与', + 2 => '干部培训', ]; $list = []; foreach ($dataArray as $key => $value) { $list[$key] = []; - // 根据固定字段映射转换数据 - foreach ($fieldMapping as $excelHeader => $dbField) { - if (isset($value[$excelHeader])) { - $list[$key][$dbField] = $value[$excelHeader]; - } + + // 只读取 Excel 中的字段:类型、课程名称、公司名称、姓名、部门 + $courseName = $value['课程名称'] ?? null; + $typeText = $value['类型'] ?? null; + + // 转换类型:中文 -> 数字 + $type = null; + if ($typeText) { + $type = $typeMapping[$typeText] ?? null; + } + + // 根据课程名称查找课程信息 + $course = null; + if ($courseName) { + $course = Course::where('name', $courseName)->first(); + } + + // 填充基础字段 + $list[$key]['type'] = $type; + $list[$key]['type_name'] = $type ? ($typeNameMapping[$type] ?? '') : ''; + $list[$key]['course_name'] = $courseName; + $list[$key]['company_name'] = $value['公司名称'] ?? null; + $list[$key]['name'] = $value['姓名'] ?? null; + $list[$key]['department'] = $value['部门'] ?? null; + + // 根据课程信息填充其他字段 + if ($course) { + $list[$key]['start_date'] = $course->start_date; + $list[$key]['end_date'] = $course->end_date; + $list[$key]['course_type_id'] = $course->type; + $list[$key]['course_id'] = $course->id; + } else { + // 如果找不到课程,字段设为 null + $list[$key]['start_date'] = null; + $list[$key]['end_date'] = null; + $list[$key]['course_type_id'] = null; + $list[$key]['course_id'] = null; } } return $this->success($list); diff --git a/app/Http/Controllers/Admin/HistoryCourseController.php b/app/Http/Controllers/Admin/HistoryCourseController.php index 68d848a..8394c96 100644 --- a/app/Http/Controllers/Admin/HistoryCourseController.php +++ b/app/Http/Controllers/Admin/HistoryCourseController.php @@ -163,7 +163,7 @@ class HistoryCourseController extends BaseController * description="", * @OA\Parameter(name="id", in="query", @OA\Schema(type="int"), required=false, description="Id(存在更新,不存在新增)"), * @OA\Parameter(name="type", in="query", @OA\Schema(type="string"), required=true, description="课程体系id"), - * @OA\Parameter(name="course_type_signs_pass", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="培养人数未去重"), + * @OA\Parameter(name="course_type_signs_pass", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="培养人次未去重"), * @OA\Parameter(name="course_type_signs_pass_unique", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="培养人数去重"), * @OA\Parameter(name="course_signs_pass", in="query", @OA\Schema(type="integer", format="int64"), required=false, description="课程培养人数"), * @OA\Parameter(name="course_name", in="query", @OA\Schema(type="string", nullable=true), description="课程名称"), diff --git a/app/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index 90271d6..83db11f 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -134,8 +134,8 @@ class OtherController extends CommonController // 校友总数 $list['schoolmate_total'] = User::where('is_schoolmate', 1)->count(); - // 今年新增校友数 - $list['schoolmate_year'] = User::where('is_schoolmate', 1)->where('created_at', 'like', '%' . date('Y') . '%')->count(); + // 今年新增校友数(根据schoolmate_time字段统计) + $list['schoolmate_year'] = User::where('is_schoolmate', 1)->whereYear('schoolmate_time', date('Y'))->count(); // 投后企业 $list['company_invested_total'] = CourseSign::yhInvestedTotal(CourseType::START_DATE, date('Y-m-d'), null); // 元和员工参与人数 @@ -165,17 +165,13 @@ class OtherController extends CommonController ->where('start_time', 'like', '%' . date('Y-m') . '%') ->get(); - // 苏州区域数据 - $suzhouArea = Company::approvedStudents()->where('company_city', '苏州市')->groupBy('company_area') - ->whereNotNull('company_area') - ->get(['company_area']); + // 苏州区域数据 - 使用 CourseSign::area 方法实现数据一致 + $areasData = CourseSign::area($start_date, $end_date, null, null, true, true); $suzhou = []; - foreach ($suzhouArea as $item) { + foreach ($areasData as $item) { $suzhou[] = [ - 'area' => $item->company_area, - 'total' => User::whereHas('company', function ($query) use ($item) { - $query->where('company_area', $item->company_area); - })->where('is_schoolmate', 1)->count() + 'area' => $item['area'], + 'total' => $item['total_unique'] ?? $item['total'] ]; } @@ -258,18 +254,28 @@ class OtherController extends CommonController $courseType->course_signs_total = $courseType->history_course_signs_total + $courseType->now_course_signs_total; } + // 统计 is_chart=0 的课程类型数据,组成"其他"统计项 + $otherCourseType = CourseType::getOtherStatistics($configStartDate, $configEndDate); + + // 将"其他"添加到 courseTypes 集合中 + $courseTypes->push($otherCourseType); + // 将统计数据直接组合到配置对象中 $config->courseTypes = $courseTypes; - // 总期数 + // 总期数(包含"其他") $config->course_periods_total = $courseTypes->sum('course_periods_total'); - // 总去重人数 + // 总去重人数(包含"其他") $coursesAll = Course::whereIn('type', $allCourseTypes->pluck('id')) ->where('is_chart', 1) ->where(function ($query) use ($configStartDate, $configEndDate) { $query->whereBetween('start_date', [$configStartDate, $configEndDate]) ->orWhereBetween('end_date', [$configStartDate, $configEndDate]); })->get(); - $config->course_signs_unique_total = $courseTypes->sum('history_course_signs_total') + CourseSign::courseSignsTotalByUnique($configStartDate, $configEndDate, 1, $coursesAll->pluck('id'), false, false); + + // 获取"其他"课程类型的课程ID列表 + $otherCourseIds = CourseType::getOtherCourseIds($configStartDate, $configEndDate); + + $config->course_signs_unique_total = $courseTypes->sum('history_course_signs_total') + CourseSign::courseSignsTotalByUnique($configStartDate, $configEndDate, 1, $coursesAll->pluck('id')->merge($otherCourseIds), false, false); } return $this->success(compact('list', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article', 'yearConfigs')); @@ -318,20 +324,25 @@ class OtherController extends CommonController // 跟班学员数(在指定时间范围内报名的学员中,from为'跟班学员'的数量) $list['ganbu_total'] = CourseSign::genban($start_date, $end_date, $course_ids); - // 今年上市公司数量(从stock_companys表获取,stock_date在今年) - $currentYear = date('Y'); - $list['company_market_year_total'] = StockCompany::whereYear('stock_date', $currentYear)->count(); + // 当前上市公司数量(从stock_companys表获取,stock_date在指定时间范围内) + $list['company_market_year_total'] = StockCompany::getByDateRange($start_date, $end_date); - // 入学后上市公司数量(从stock_companys表获取,is_after_enrollment = 1) - $list['company_market_after_enrollment_total'] = StockCompany::where('is_after_enrollment', 1)->count(); + // 入学后上市公司数量(从stock_companys表获取,is_after_enrollment = 1,stock_date在指定时间范围内) + $list['company_market_after_enrollment_total'] = StockCompany::getAfterEnrollment($start_date, $end_date); - // 累计被投企业数 + // 累计被投企业数,被投企业覆盖数 $list['course_signs_invested'] = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $course_ids); - // 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) + // 当前被投企业覆盖数(根据传入的开始结束时间统计) + $list['course_signs_invested_current'] = CourseSign::yhInvestedTotal($start_date, $end_date, $course_ids); + + // 当前入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) $list['company_invested_after_enrollment_total'] = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids); - // 今年被投企业数 + // 累计入学后被投企业数量(从CourseType::START_DATE开始的入学后被投企业数量) + $list['company_invested_after_enrollment_total_cumulative'] = CourseSign::companyInvestedAfterEnrollment(CourseType::START_DATE, $end_date, $course_ids); + + // 当前被投企业数 $list['company_invested_year_total'] = CourseSign::companyInvestedYear($start_date, $end_date, $course_ids); // 元和员工参与人数 @@ -373,7 +384,7 @@ class OtherController extends CommonController // 被投企业数 'yh_invested_total' => CourseSign::yhInvested($start_date, $end_date, [$course->id]), // 元禾同事数 - 'company_join_total' => CourseSign::companyJoin($start_date, $end_date, [$course->id], false, false, false), + 'company_join_total' => CourseSign::companyJoin($start_date, $end_date, [$course->id], false, false), ]; } } @@ -410,10 +421,13 @@ class OtherController extends CommonController // 区域明细统计 $areas = CourseSign::area($start_date, $end_date, 1, $courses->pluck('id'), true); + // 校友的区域明细统计 + $areas_schoolmate = CourseSign::area($start_date, $end_date, 1, $courses->pluck('id'), true, true); + // 获取统计项元数据 $statistics_metadata = Course::getStatisticsMetadata(); - return $this->success(compact('list', 'courseTypesSum', 'areas', 'statistics_metadata')); + return $this->success(compact('list', 'courseTypesSum', 'areas', 'areas_schoolmate', 'statistics_metadata')); } /** @@ -457,107 +471,119 @@ class OtherController extends CommonController // 被投企业明细 - 使用与coursesHome相同的算法 // 确保 $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(); - - // 公司基本信息 - $companyInfo = [ - 'company_name' => $company->company_name, - 'company_legal_representative' => $company->company_legal_representative ?? '', - 'company_date' => $company->company_date ?? '', - 'company_address' => $company->company_address ?? '', - 'business_scope' => $company->business_scope ?? '', - 'contact_phone' => $company->contact_phone ?? '', - 'contact_mail' => $company->contact_mail ?? '', - 'company_tag' => $company->company_tag ?? '', - // 'credit_code' => ($company->credit_code ? ' ' . $company->credit_code : ''), - ]; - if (empty($userIds)) { - // 如果没有学员,仍然导出公司基本信息 - $data[] = array_merge($companyInfo, [ - 'user_name' => '', - 'course_names' => '', - 'course_types' => '', - 'course_count' => 0, - ]); - } else { - $userCourseSigns = CourseSign::getStudentList(CourseType::START_DATE, $end_date, 1, $courseIdsArray) - ->whereIn('user_id', $userIds) - ->with(['user', 'course.typeDetail']) - ->get(); + // 辅助函数:构建被投企业数据 + $buildInvestedCompanyData = function ($start_date, $end_date, $courseIdsArray) { + $data = []; + $companies = CourseSign::yhInvestedTotal($start_date, $end_date, $courseIdsArray, true); + + foreach ($companies as $company) { + // 获取该公司的学员信息 + $userIds = User::where('company_id', $company->id)->pluck('id')->toArray(); + + // 公司基本信息 + $companyInfo = [ + 'company_name' => $company->company_name, + 'company_legal_representative' => $company->company_legal_representative ?? '', + 'company_date' => $company->company_date ?? '', + 'company_address' => $company->company_address ?? '', + 'business_scope' => $company->business_scope ?? '', + 'contact_phone' => $company->contact_phone ?? '', + 'contact_mail' => $company->contact_mail ?? '', + 'company_tag' => $company->company_tag ?? '', + ]; - // 按学员分组 - $usersData = []; - foreach ($userCourseSigns as $sign) { - if (!$sign->user) { - continue; + if (empty($userIds)) { + // 如果没有学员,仍然导出公司基本信息 + $data[] = array_merge($companyInfo, [ + 'user_name' => '', + 'course_names' => '', + 'course_types' => '', + 'course_count' => 0, + ]); + } else { + $userCourseSigns = CourseSign::getStudentList($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; } - $userId = $sign->user_id; - if (!isset($usersData[$userId])) { - $usersData[$userId] = [ - 'user' => $sign->user, - 'courseSigns' => [], - ]; + + // 每个学员一行,公司信息只在第一行显示,后续行公司信息为空 + $isFirstRow = true; + foreach ($usersData as $userData) { + $user = $userData['user']; + $courseSigns = collect($userData['courseSigns']); + + if ($courseSigns->isNotEmpty()) { + // 获取课程名称列表,用中文顿号分隔 + $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' => '', + 'business_scope' => '', + 'contact_phone' => '', + 'contact_mail' => '', + 'company_tag' => '', + 'credit_code' => '', + 'user_name' => $user->name ?? '', + 'course_names' => $courseNames, + 'course_types' => $courseTypes, + 'course_count' => $courseCount, + ]; + } + } } - $usersData[$userId]['courseSigns'][] = $sign; } + } - // 每个学员一行,公司信息只在第一行显示,后续行公司信息为空 - $isFirstRow = true; - foreach ($usersData as $userData) { - $user = $userData['user']; - $courseSigns = collect($userData['courseSigns']); + return $data; + }; - if ($courseSigns->isNotEmpty()) { - // 获取课程名称列表,用中文顿号分隔 - $courseNames = $courseSigns->pluck('course.name')->filter()->unique()->values()->implode('、'); + // 第一个 sheet:累计被投企业覆盖数(从 CourseType::START_DATE 开始) + $cumulativeData = $buildInvestedCompanyData(CourseType::START_DATE, $end_date, $courseIdsArray); - // 获取课程体系列表,用中文顿号分隔 - $courseTypes = $courseSigns->pluck('course.typeDetail.name') - ->filter() - ->unique() - ->values() - ->implode('、'); + // 第二个 sheet:当前被投企业覆盖数(从传入的开始日期开始) + $currentData = $buildInvestedCompanyData($start_date, $end_date, $courseIdsArray); - // 报名课程数 - $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' => '', - 'business_scope' => '', - 'contact_phone' => '', - 'contact_mail' => '', - 'company_tag' => '', - 'credit_code' => '', - 'user_name' => $user->name ?? '', - 'course_names' => $courseNames, - 'course_types' => $courseTypes, - 'course_count' => $courseCount, - ]; - } - } - } - } - } $fields = [ 'company_name' => '企业名称', 'company_legal_representative' => '法人', @@ -567,13 +593,25 @@ class OtherController extends CommonController 'contact_phone' => '联系电话', 'contact_mail' => '联系邮箱', 'company_tag' => '企业资质', - // 'credit_code' => '统一社会信用代码', 'user_name' => '学员姓名', 'course_names' => '课程名称', 'course_types' => '课程体系', 'course_count' => '报名课程数', ]; + + // 创建多 sheet 导出 + $sheets = [ + new SheetExport($cumulativeData, $fields, '被投企业覆盖数'), + new SheetExport($currentData, $fields, '当前被投企业覆盖数'), + ]; + $filename = '被投企业明细'; + + // 直接返回多 sheet 导出 + return Excel::download( + new MultiSheetExport($sheets), + $filename . '_' . date('YmdHis') . '.xlsx' + ); break; case 'course_signs_total': @@ -668,7 +706,7 @@ class OtherController extends CommonController 'course_name' => '课程名称', 'start_time' => '开始时间', 'end_time' => '结束时间', - 'course_type_signs_pass' => '培养人数未去重', + 'course_type_signs_pass' => '培养人次未去重', 'course_type_signs_pass_unique' => '培养人数去重', 'course_signs_pass' => '课程培养人数', ]; @@ -806,7 +844,7 @@ class OtherController extends CommonController 'course_name' => '课程名称', 'start_time' => '开始时间', 'end_time' => '结束时间', - 'course_type_signs_pass' => '培养人数未去重', + 'course_type_signs_pass' => '培养人次未去重', 'course_type_signs_pass_unique' => '培养人数去重', 'course_signs_pass' => '课程培养人数', ]; @@ -838,7 +876,7 @@ class OtherController extends CommonController $query->whereBetween('start_date', [$start_date, $end_date]) ->orWhereBetween('end_date', [$start_date, $end_date]); } - })->orderBy('start_date', 'asc')->get(); + })->where('is_chart', 1)->orderBy('start_date', 'asc')->get(); foreach ($courses2 as $course) { $data[] = [ 'course_type' => $courseType->name, @@ -1101,8 +1139,7 @@ class OtherController extends CommonController case 'company_market_year_total': // 今年上市公司明细 - 从stock_companys表获取,与coursesHome统计逻辑保持一致 - $currentYear = date('Y'); - $stockCompanies = StockCompany::with('company')->whereYear('stock_date', $currentYear)->get(); + $stockCompanies = StockCompany::getByDateRange($start_date, $end_date, true)->load('company'); foreach ($stockCompanies as $stockCompany) { // 通过company_id关联到Company表获取详细信息 @@ -1222,7 +1259,7 @@ class OtherController extends CommonController case 'company_market_after_enrollment_total': // 入学后上市公司明细 - 从stock_companys表获取,与coursesHome统计逻辑保持一致 - $stockCompanies = StockCompany::with('company')->where('is_after_enrollment', 1)->get(); + $stockCompanies = StockCompany::getAfterEnrollment($start_date, $end_date, true)->load('company'); foreach ($stockCompanies as $stockCompany) { // 通过company_id关联到Company表获取详细信息 @@ -1345,23 +1382,36 @@ class OtherController extends CommonController case 'company_invested_after_enrollment_total': // 入学后被投企业明细 - 使用模型方法 - $companiesAfterEnrollment = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids, true); + // 辅助函数:构建入学后被投企业数据 + $buildAfterEnrollmentData = function ($start_date, $end_date, $course_ids) { + $data = []; + $companiesAfterEnrollment = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids, true); + + foreach ($companiesAfterEnrollment as $item) { + $company = $item['company']; + $userNames = collect($item['users'])->pluck('name')->filter()->unique()->implode("\n\r"); + $data[] = [ + 'company_name' => $company->company_name, + 'company_legal_representative' => $company->company_legal_representative ?? '', + 'company_date' => $company->company_date ?? '', + 'invest_date' => $item['invest_date'] ?? '', + 'first_sign_date' => $item['first_enrollment_date'] ?? '', + 'user_names' => $userNames, + 'company_address' => $company->company_address ?? '', + 'company_city' => $company->company_city ?? '', + 'company_area' => $company->company_area ?? '', + ]; + } + + return $data; + }; + + // 第一个 sheet:当前入学后被投企业(从传入的开始日期开始) + $currentData = $buildAfterEnrollmentData($start_date, $end_date, $course_ids); + + // 第二个 sheet:累计入学后被投企业(从 CourseType::START_DATE 开始) + $cumulativeData = $buildAfterEnrollmentData(CourseType::START_DATE, $end_date, $course_ids); - foreach ($companiesAfterEnrollment as $item) { - $company = $item['company']; - $userNames = collect($item['users'])->pluck('name')->filter()->unique()->implode("\n\r"); - $data[] = [ - 'company_name' => $company->company_name, - 'company_legal_representative' => $company->company_legal_representative ?? '', - 'company_date' => $company->company_date ?? '', - 'invest_date' => $item['invest_date'] ?? '', - 'first_sign_date' => $item['first_enrollment_date'] ?? '', - 'user_names' => $userNames, - 'company_address' => $company->company_address ?? '', - 'company_city' => $company->company_city ?? '', - 'company_area' => $company->company_area ?? '', - ]; - } $fields = [ 'company_name' => '企业名称', 'company_legal_representative' => '法人', @@ -1373,7 +1423,20 @@ class OtherController extends CommonController 'company_city' => '所在城市', 'company_area' => '所在区域', ]; + + // 创建多 sheet 导出 + $sheets = [ + new SheetExport($currentData, $fields, '当前入学后被投企业'), + new SheetExport($cumulativeData, $fields, '累计入学后被投企业'), + ]; + $filename = '入学后被投企业明细'; + + // 直接返回多 sheet 导出 + return Excel::download( + new MultiSheetExport($sheets), + $filename . '_' . date('YmdHis') . '.xlsx' + ); break; case 'company_invested_year_total': diff --git a/app/Http/Controllers/Admin/StatisticsMetadataController.php b/app/Http/Controllers/Admin/StatisticsMetadataController.php new file mode 100644 index 0000000..fb10b9e --- /dev/null +++ b/app/Http/Controllers/Admin/StatisticsMetadataController.php @@ -0,0 +1,151 @@ +all(); + DB::beginTransaction(); + try { + // 验证必填字段 + if (empty($all['key'])) { + return $this->fail([ResponseCode::ERROR_PARAMETER, '统计项标识不能为空']); + } + if (empty($all['name'])) { + return $this->fail([ResponseCode::ERROR_PARAMETER, '统计项名称不能为空']); + } + + if (isset($all['id'])) { + $model = $this->model->find($all['id']); + if (empty($model)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']); + } + // 更新时检查 key 是否重复(排除自己) + $exists = StatisticsMetadata::where('key', $all['key']) + ->where('id', '!=', $all['id']) + ->first(); + if ($exists) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '统计项标识已存在']); + } + } else { + $model = $this->model; + // 新增时检查 key 是否重复 + $exists = StatisticsMetadata::where('key', $all['key'])->first(); + if ($exists) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '统计项标识已存在']); + } + } + + $original = $model->getOriginal(); + $model->fill($all); + $model->save(); + DB::commit(); + // 记录日志 + $this->saveLogs($original, $model); + return $this->success($model); + } catch (\Exception $exception) { + DB::rollBack(); + return $this->fail([$exception->getCode(), $exception->getMessage()]); + } + } + + /** + * @OA\Get( + * path="/api/admin/statistics-metadata/destroy", + * tags={"统计指标管理"}, + * summary="删除", + * description="", + * @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), + * @OA\Response( + * response="200", + * description="暂无" + * ) + * ) + */ + public function destroy() + { + return parent::destroy(); + } + +} diff --git a/app/Models/Calendar.php b/app/Models/Calendar.php index b247246..b1cbe50 100755 --- a/app/Models/Calendar.php +++ b/app/Models/Calendar.php @@ -87,6 +87,26 @@ class Calendar extends SoftDeletesModel }); } + /** + * 获取开课统计(场次数量或天数总和) + * @param string|null $start_date 开始日期 + * @param string|null $end_date 结束日期 + * @param array|null $course_type_id 课程体系ID数组 + * @param string $type 统计类型:'count' 统计次数,'sum' 统计天数总和 + * @return int|float + */ + public static function getCourseStatistics($start_date = null, $end_date = null, $course_type_id = null, $type = 'count') + { + $query = self::getCalendarsByDateRange($start_date, $end_date, $course_type_id) + ->where('is_count_days', 1); + + if ($type === 'sum') { + return $query->sum('days'); + } + + return $query->count(); + } + /** * 获取开课场次数量 * @param string|null $start_date 开始日期 @@ -96,7 +116,7 @@ class Calendar extends SoftDeletesModel */ public static function getCourseTotal($start_date = null, $end_date = null, $course_type_id = null) { - return self::getCalendarsByDateRange($start_date, $end_date, $course_type_id)->count(); + return self::getCourseStatistics($start_date, $end_date, $course_type_id, 'count'); } /** @@ -108,9 +128,7 @@ class Calendar extends SoftDeletesModel */ public static function getCourseDayTotal($start_date = null, $end_date = null, $course_type_id = null) { - return self::getCalendarsByDateRange($start_date, $end_date, $course_type_id) - ->where('is_count_days', 1) - ->sum('days'); + return self::getCourseStatistics($start_date, $end_date, $course_type_id, 'sum'); } } diff --git a/app/Models/CourseSign.php b/app/Models/CourseSign.php index 7836291..b83e40a 100755 --- a/app/Models/CourseSign.php +++ b/app/Models/CourseSign.php @@ -188,33 +188,6 @@ class CourseSign extends SoftDeletesModel $companies = Company::approvedStudents()->whereHas('users', function ($query) use ($userIds) { $query->whereIn('id', $userIds); })->where('is_yh_invested', 1)->get(); - // 自定义时间:需要按被投时间筛选 - // 筛选出被投时间在范围内的企业 - $filteredCompanies = []; - foreach ($companies as $company) { - $projectUsers = $company->project_users ?? []; - $hasValidInvestDate = false; - $allInvestDatesNull = true; - - foreach ($projectUsers as $item) { - $investDate = $item['investDate'] ?? null; - // 检查是否有有效的被投时间 - if ($investDate) { - $allInvestDatesNull = false; - // 检查被投时间是否在范围内 - if ($investDate <= $end_date) { - $hasValidInvestDate = true; - break; // 只要有一条满足就加入 - } - } - } - - // 如果有有效的被投时间在范围内,或者所有被投时间都是null,则加入结果 - if ($hasValidInvestDate || $allInvestDatesNull) { - $filteredCompanies[] = $company; - } - } - $companies = collect($filteredCompanies); // 返回结果 if ($retList) { @@ -248,25 +221,6 @@ class CourseSign extends SoftDeletesModel $companies = Company::approvedStudents()->whereHas('users', function ($query) use ($userIds) { $query->whereIn('id', $userIds); })->where('is_yh_invested', 1)->get(); - // 自定义时间:需要按被投时间筛选 - if (!$isDefaultDate) { - $startDate = substr($start_date, 0, 10); - $endDate = substr($end_date, 0, 10); - // 筛选出被投时间在范围内的企业 - $filteredCompanies = []; - foreach ($companies as $company) { - $projectUsers = $company->project_users ?? []; - foreach ($projectUsers as $item) { - $investDate = $item['investDate'] ?? null; - // 检查被投时间是否在范围内 - if ($investDate && $investDate >= $startDate && $investDate <= $endDate) { - $filteredCompanies[] = $company; - break; // 只要有一条满足就加入 - } - } - } - $companies = collect($filteredCompanies); - } // 返回结果 if ($retList) { @@ -416,7 +370,16 @@ class CourseSign extends SoftDeletesModel if ($retList) { return User::with('company')->whereIn('id', $courseSigns->pluck('user_id'))->get(); } else { - return User::whereIn('id', $courseSigns->pluck('user_id'))->count(); + $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'); + return $baseCount + $traineeStudentTotal; } } @@ -473,63 +436,84 @@ class CourseSign extends SoftDeletesModel } /** - * 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) + * 当前入学后被投企业数量(被投时间在指定时间范围内,且学员入学时间小于等于被投时间的公司数量) * @param string $start_date 开始日期 * @param string $end_date 结束日期 - * @param array|null $course_ids 课程ID数组,不传则统计所有课程 + * @param array|null $course_ids 课程ID数组(已废弃,保留以兼容) * @param bool $retList 是否返回列表,false返回数量,true返回列表 * @return int|array */ public static function companyInvestedAfterEnrollment($start_date, $end_date, $course_ids = null, $retList = false) { - $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); - $courseSignsForInvest = $courseSignsQuery->with(['user.company', 'course'])->get(); + // 获取所有被投企业 + $companies = Company::approvedStudents()->where('is_yh_invested', 1)->get(); $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; - } + foreach ($companies as $company) { + $projectUsers = $company->project_users ?? []; - // 从 project_users 中检查是否有任何一个被投时间 >= 课程开始时间(入学时间) - $projectUsers = $sign->user->company->project_users; - $hasInvestAfterEnrollment = false; - $investDate = null; - if (!empty($projectUsers) && is_array($projectUsers)) { - foreach ($projectUsers as $projectUser) { - if (!empty($projectUser['investDate'])) { - $currentInvestDate = $projectUser['investDate']; - // 只要有一个被投时间 >= 课程开始时间(入学时间),就满足条件 - if ($currentInvestDate >= $enrollmentDate) { - $hasInvestAfterEnrollment = true; - // 记录满足条件的被投时间(如果有多个,记录最早的) - if ($investDate === null || $currentInvestDate < $investDate) { - $investDate = $currentInvestDate; + // 遍历被投时间,找到在指定时间范围内的被投时间 + foreach ($projectUsers as $projectUser) { + $investDate = $projectUser['investDate'] ?? null; + + // 检查被投时间是否在指定时间范围内 + if ($investDate && $investDate >= $start_date && $investDate <= $end_date) { + // 获取该公司的所有学员 + $users = $company->users()->get(); + + // 检查是否有学员的入学时间(课程开始时间)小于等于被投时间 + $hasValidEnrollment = false; + $firstEnrollmentDate = null; + $matchedUsers = []; + + foreach ($users as $user) { + // 获取该学员的所有审核通过的课程报名记录 + $courseSigns = self::where('user_id', $user->id) + ->where('status', 1) + ->with('course') + ->get(); + + foreach ($courseSigns as $courseSign) { + if ($courseSign->course && $courseSign->course->start_date) { + $enrollmentDate = \Carbon\Carbon::parse($courseSign->course->start_date)->format('Y-m-d'); + + // 检查入学时间是否小于等于被投时间 + if ($enrollmentDate <= $investDate) { + $hasValidEnrollment = true; + // 记录最早的入学时间 + if ($firstEnrollmentDate === null || $enrollmentDate < $firstEnrollmentDate) { + $firstEnrollmentDate = $enrollmentDate; + } + if ($retList) { + $matchedUsers[] = $user; + } + break; // 只要有一个课程满足条件即可 } } } } - } - // 只要有一个被投时间 >= 课程开始时间(入学时间),说明是入学后被投 - if ($hasInvestAfterEnrollment && $investDate) { - $companyId = $sign->user->company->id; - if (!isset($companiesAfterEnrollment[$companyId])) { - $companiesAfterEnrollment[$companyId] = [ - 'company' => $sign->user->company, - 'first_enrollment_date' => $enrollmentDate, // 首次入学时间(课程开始时间) - 'invest_date' => $investDate, - 'users' => [], - ]; - } - if ($retList) { - $companiesAfterEnrollment[$companyId]['users'][] = $sign->user; + + // 如果有学员的入学时间小于等于被投时间,则计入统计 + if ($hasValidEnrollment) { + $companyId = $company->id; + if (!isset($companiesAfterEnrollment[$companyId])) { + $companiesAfterEnrollment[$companyId] = [ + 'company' => $company, + 'first_enrollment_date' => $firstEnrollmentDate, + 'invest_date' => $investDate, + 'users' => [], + ]; + } + if ($retList) { + // 合并用户列表(去重) + $existingUserIds = array_column($companiesAfterEnrollment[$companyId]['users'], 'id'); + foreach ($matchedUsers as $user) { + if (!in_array($user->id, $existingUserIds)) { + $companiesAfterEnrollment[$companyId]['users'][] = $user; + } + } + } + break; // 只要有一个被投时间满足条件即可 } } } @@ -548,20 +532,55 @@ class CourseSign extends SoftDeletesModel * @param string $end_date 结束日期 * @param array|null $course_ids 课程ID数组,不传则统计所有课程 * @param bool $retList 是否返回列表,false返回详情,true返回列表 + * @param bool $is_schoolmate 是否只统计校友,false统计所有,true只统计校友 */ - public static function area($start_date, $end_date, $status = null, $course_ids = null, $retList = false) + public static function area($start_date, $end_date, $status = null, $course_ids = null, $retList = false, $is_schoolmate = false) { $courseSignList = self::getStudentList($start_date, $end_date, $status, $course_ids); - // 地区 - $suzhouArea = Company::approvedStudents()->where('company_city', '苏州市')->pluck('company_area')->unique(); + // 如果只统计校友,添加校友筛选条件 + if ($is_schoolmate) { + $courseSignList->whereHas('user', function ($query) { + $query->where('is_schoolmate', 1); + }); + } + + // 地区 - 从数据字典获取 + $suzhouAreas = ParameterDetail::where('parameter_id', 5) + ->where('status', 1) + ->orderBy('sort', 'asc') + ->pluck('value') + ->toArray(); + $list = []; - foreach ($suzhouArea as $area) { - $sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) use ($area) { - $query->whereHas('company', function ($query) use ($area) { - $query->where('company_area', $area); - }); - })->get(); + foreach ($suzhouAreas as $area) { + // 特殊处理:高新区需要统计高新区+虎丘区 + if ($area === '高新区') { + $sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) { + $query->whereHas('company', function ($query) { + $query->where('company_city', '苏州市') + ->where(function ($q) { + $q->where('company_area', '高新区') + ->orWhere('company_area', '虎丘区'); + }); + }); + })->get(); + } elseif ($area === '苏州市外') { + // 苏州市外:统计除了苏州之外的所有数据 + $sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) { + $query->whereHas('company', function ($query) { + $query->where('company_city', '!=', '苏州市'); + }); + })->get(); + } else { + // 其他区域:正常统计 + $sourseSignList2 = (clone $courseSignList)->whereHas('user', function ($query) use ($area) { + $query->whereHas('company', function ($query) use ($area) { + $query->where('company_city', '苏州市') + ->where('company_area', 'like', '%' . $area . '%'); + }); + })->get(); + } $list[] = [ 'area' => $area, @@ -570,19 +589,7 @@ class CourseSign extends SoftDeletesModel // 已去重 'total_unique' => User::whereIn('id', $sourseSignList2->pluck('user_id'))->groupBy('mobile')->get()->count(), ]; - } - $courseSignList3 = (clone $courseSignList)->whereHas('user', function ($query) { - $query->whereHas('company', function ($query) { - $query->where('company_city', '!=', '苏州市'); - }); - })->get(); - $list[] = [ - 'area' => '苏州市外', - 'total' => $courseSignList3->count(), - // 已去重 - 'total_unique' => User::groupBy('mobile')->whereIn('id', $courseSignList3->pluck('user_id'))->get()->count(), - ]; if ($retList) { // 返回列表 @@ -601,24 +608,35 @@ class CourseSign extends SoftDeletesModel $courseSignsQuery = self::getStudentList($start_date, $end_date, null, $course_ids); $courseSignByType = $courseSignsQuery->get(); - // 检测关键词 - $companyNameKeyword = [ - '元禾控股', - '元禾原点', - '元禾厚望', - '元禾重元', - '元禾璞华', - '元禾谷风', - '元禾绿柳', - '元禾辰坤', - '元禾沙湖', - '禾裕集团', - '苏州科服', - '信诚管理咨询', - '集成电路公司', - '常州团队', - '国器元禾' - ]; + // 从 config 表中获取公司名称关键词 + $companyNameKeyword = []; + $configValue = Config::getValueByKey('yuanhe_company'); + if ($configValue) { + $configData = json_decode($configValue, true); + if (is_array($configData)) { + foreach ($configData as $item) { + // 提取 company_name 和 short_company_name,过滤空值 + if (!empty($item['company_name'])) { + $companyNameKeyword[] = $item['company_name']; + } + if (!empty($item['short_company_name'])) { + $companyNameKeyword[] = $item['short_company_name']; + } + } + // 去重关键词并重新索引 + $companyNameKeyword = array_values(array_unique($companyNameKeyword)); + } + } + + // 如果 config 中没有数据,直接返回空 + if (empty($companyNameKeyword)) { + if ($retList) { + return collect([]); + } else { + return 0; + } + } + $list = User::whereIn('id', $courseSignByType->pluck('user_id')) ->where(function ($query) use ($companyNameKeyword) { foreach ($companyNameKeyword as $item) { @@ -692,10 +710,7 @@ class CourseSign extends SoftDeletesModel */ public static function isSuzhou($user = null, $company = null) { - // 判断是否为苏州地区:user表的company_address包含"苏州",或关联公司的company_address包含"苏州",或company_city包含"苏州" - if ($user && $user->company_address && strpos($user->company_address, '苏州') !== false) { - return true; - } + // 判断是否为苏州地区:关联公司的company_address包含"苏州",或company_city包含"苏州" if ($company && $company->company_address && strpos($company->company_address, '苏州') !== false) { return true; } diff --git a/app/Models/CourseType.php b/app/Models/CourseType.php index 9354b6f..97d6d4e 100755 --- a/app/Models/CourseType.php +++ b/app/Models/CourseType.php @@ -23,5 +23,94 @@ class CourseType extends SoftDeletesModel return $this->hasMany(Course::class, 'type', 'id'); } + /** + * 获取"其他"统计项(is_chart=0 的课程类型合并统计) + * @param string $startDate 开始日期 + * @param string $endDate 结束日期 + * @return CourseType + */ + public static function getOtherStatistics($startDate, $endDate) + { + // 获取所有 is_chart=0 的课程类型 + $otherCourseTypes = self::where('is_chart', 0) + ->where('is_history', 0) + ->get(); + + $otherCourseTypeIds = $otherCourseTypes->pluck('id')->toArray(); + + // 如果没有"其他"课程类型,返回空统计 + if (empty($otherCourseTypeIds)) { + $otherCourseType = new self(); + $otherCourseType->id = 0; + $otherCourseType->name = '其他'; + $otherCourseType->history_course_periods_total = 0; + $otherCourseType->now_course_periods_total = 0; + $otherCourseType->history_course_signs_total = 0; + $otherCourseType->now_course_signs_total = 0; + $otherCourseType->course_periods_total = 0; + $otherCourseType->course_signs_total = 0; + return $otherCourseType; + } + + // 历史课程数据(所有 is_chart=0 的课程类型) + $otherHistoryCourse = HistoryCourse::whereIn('type', $otherCourseTypeIds) + ->where(function ($query) use ($startDate, $endDate) { + $query->whereBetween('start_time', [$startDate, $endDate]) + ->orWhereBetween('end_time', [$startDate, $endDate]); + })->get(); + + // 实际课程数据(所有 is_chart=0 的课程类型下的课程) + $otherCourses = Course::whereIn('type', $otherCourseTypeIds)->where('is_chart', 1) + ->where(function ($query) use ($startDate, $endDate) { + $query->whereBetween('start_date', [$startDate, $endDate]) + ->orWhereBetween('end_date', [$startDate, $endDate]); + })->get(); + + $otherHistoryCoursePeriodsTotal = $otherHistoryCourse->count(); + $otherNowCoursePeriodsTotal = $otherCourses->count(); + $otherHistoryCourseSignsTotal = $otherHistoryCourse->sum('course_type_signs_pass_unique'); + $otherNowCourseSignsTotal = CourseSign::courseSignsTotalByUnique($startDate, $endDate, 1, $otherCourses->pluck('id'), false, false); + + // 创建"其他"统计项 + $otherCourseType = new self(); + $otherCourseType->id = 0; + $otherCourseType->name = '其他'; + $otherCourseType->history_course_periods_total = $otherHistoryCoursePeriodsTotal; + $otherCourseType->now_course_periods_total = $otherNowCoursePeriodsTotal; + $otherCourseType->history_course_signs_total = $otherHistoryCourseSignsTotal; + $otherCourseType->now_course_signs_total = $otherNowCourseSignsTotal; + $otherCourseType->course_periods_total = $otherCourseType->now_course_periods_total + $otherCourseType->history_course_periods_total; + $otherCourseType->course_signs_total = $otherCourseType->history_course_signs_total + $otherCourseType->now_course_signs_total; + + return $otherCourseType; + } + + /** + * 获取"其他"课程类型的课程ID列表(用于计算总去重人数) + * @param string $startDate 开始日期 + * @param string $endDate 结束日期 + * @return \Illuminate\Support\Collection + */ + public static function getOtherCourseIds($startDate, $endDate) + { + // 获取所有 is_chart=0 的课程类型ID + $otherCourseTypeIds = self::where('is_chart', 0) + ->where('is_history', 0) + ->pluck('id')->toArray(); + + if (empty($otherCourseTypeIds)) { + return collect([]); + } + + // 获取"其他"课程类型的课程数据 + $otherCoursesAll = Course::whereIn('type', $otherCourseTypeIds) + ->where('is_chart', 1) + ->where(function ($query) use ($startDate, $endDate) { + $query->whereBetween('start_date', [$startDate, $endDate]) + ->orWhereBetween('end_date', [$startDate, $endDate]); + })->get(); + + return $otherCoursesAll->pluck('id'); + } } diff --git a/app/Models/StatisticsMetadata.php b/app/Models/StatisticsMetadata.php index ba138f3..8f7a26f 100644 --- a/app/Models/StatisticsMetadata.php +++ b/app/Models/StatisticsMetadata.php @@ -6,6 +6,14 @@ class StatisticsMetadata extends SoftDeletesModel { protected $table = 'statistics_metadata'; + protected $fillable = [ + 'key', + 'name', + 'from', + 'verify', + 'remark', + ]; + /** * 根据 key 获取统计元数据 * @param string $key diff --git a/app/Models/StockCompany.php b/app/Models/StockCompany.php index e114974..644881f 100644 --- a/app/Models/StockCompany.php +++ b/app/Models/StockCompany.php @@ -27,5 +27,46 @@ class StockCompany extends SoftDeletesModel { return $this->belongsTo(Company::class, 'company_id', 'id'); } + + /** + * 获取指定时间范围内的上市公司数量(统计或列表) + * @param string|null $start_date 开始日期 + * @param string|null $end_date 结束日期 + * @param bool $retList 是否返回列表,false返回数量,true返回列表 + * @return int|\Illuminate\Database\Eloquent\Collection + */ + public static function getByDateRange($start_date = null, $end_date = null, $retList = false) + { + $query = self::when($start_date && $end_date, function ($query) use ($start_date, $end_date) { + $query->whereBetween('stock_date', [$start_date, $end_date]); + }); + + if ($retList) { + return $query->get(); + } else { + return $query->count(); + } + } + + /** + * 获取入学后上市的公司数量(统计或列表) + * @param string|null $start_date 开始日期 + * @param string|null $end_date 结束日期 + * @param bool $retList 是否返回列表,false返回数量,true返回列表 + * @return int|\Illuminate\Database\Eloquent\Collection + */ + public static function getAfterEnrollment($start_date = null, $end_date = null, $retList = false) + { + $query = self::where('is_after_enrollment', 1) + ->when($start_date && $end_date, function ($query) use ($start_date, $end_date) { + $query->whereBetween('stock_date', [$start_date, $end_date]); + }); + + if ($retList) { + return $query->get(); + } else { + return $query->count(); + } + } } diff --git a/database/migrations/2025_11_24_105100_create_history_courses_table.php b/database/migrations/2025_11_24_105100_create_history_courses_table.php index 2cacc5c..e8945d7 100644 --- a/database/migrations/2025_11_24_105100_create_history_courses_table.php +++ b/database/migrations/2025_11_24_105100_create_history_courses_table.php @@ -15,7 +15,7 @@ return new class extends Migration { Schema::create('history_courses', function (Blueprint $table) { $table->id(); $table->string('type')->comment('课程体系ID'); - $table->integer('course_type_signs_pass')->default(0)->comment('培养人数未去重'); + $table->integer('course_type_signs_pass')->default(0)->comment('培养人次未去重'); $table->integer('course_type_signs_pass_unique')->default(0)->comment('培养人数去重'); $table->integer('course_signs_pass')->default(0)->comment('课程培养人数'); // 课程名称 diff --git a/database/migrations/2026_01_17_133320_add_course_id_to_employee_participations_table.php b/database/migrations/2026_01_17_133320_add_course_id_to_employee_participations_table.php new file mode 100644 index 0000000..fa978df --- /dev/null +++ b/database/migrations/2026_01_17_133320_add_course_id_to_employee_participations_table.php @@ -0,0 +1,31 @@ +integer('course_id')->nullable()->comment('课程ID')->after('course_type_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('employee_participations', function (Blueprint $table) { + $table->dropColumn('course_id'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 7be9ba6..446bae2 100755 --- a/routes/api.php +++ b/routes/api.php @@ -242,6 +242,12 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () { Route::post('company/save', [\App\Http\Controllers\Admin\CompanyController::class, "save"]); Route::get('company/destroy', [\App\Http\Controllers\Admin\CompanyController::class, "destroy"]); + // 统计指标管理 + Route::get('statistics-metadata/index', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "index"]); + Route::get('statistics-metadata/show', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "show"]); + Route::post('statistics-metadata/save', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "save"]); + Route::get('statistics-metadata/destroy', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "destroy"]); + // 上市公司管理 Route::get('stock-company/index', [\App\Http\Controllers\Admin\StockCompanyController::class, "index"]); Route::get('stock-company/show', [\App\Http\Controllers\Admin\StockCompanyController::class, "show"]); diff --git a/数据库索引优化建议.sql b/数据库索引优化建议.sql deleted file mode 100644 index 4c144b3..0000000 --- a/数据库索引优化建议.sql +++ /dev/null @@ -1,295 +0,0 @@ --- ============================================ --- 数据库索引优化建议 --- 基于 coursesHome 统计逻辑分析 --- ============================================ - --- ============================================ --- 1. course_signs 表索引 --- ============================================ - --- 1.1 核心查询索引:status + course_id(最常用) --- 用于:courseSignsTotal, courseSignsTotalByUnique, getStudentList --- 查询条件:status, course_id, whereHas('course') -ALTER TABLE `course_signs` -ADD INDEX `idx_status_course_id` (`status`, `course_id`); - --- 1.2 用户去重查询索引:user_id + status --- 用于:courseSignsTotalByUnique(按手机号去重) -ALTER TABLE `course_signs` -ADD INDEX `idx_user_id_status` (`user_id`, `status`); - --- 1.3 排除状态索引:status(用于 whereNotIn status [4,5,6]) --- 注意:如果 status 字段选择性不高,可考虑与 course_id 组合 -ALTER TABLE `course_signs` -ADD INDEX `idx_status_not_in` (`status`); - --- 1.4 复合索引:status + course_id + user_id(用于关联查询优化) -ALTER TABLE `course_signs` -ADD INDEX `idx_status_course_user` (`status`, `course_id`, `user_id`); - --- ============================================ --- 2. courses 表索引 --- ============================================ - --- 2.1 图表统计索引:is_chart + 日期范围 --- 用于:getStudentList 中的 whereHas('course') --- 查询条件:is_chart=1, start_date/end_date BETWEEN -ALTER TABLE `courses` -ADD INDEX `idx_is_chart_dates` (`is_chart`, `start_date`, `end_date`); - --- 2.2 课程体系索引:type + is_chart --- 用于:按课程体系筛选课程 -ALTER TABLE `courses` -ADD INDEX `idx_type_is_chart` (`type`, `is_chart`); - --- 2.3 复合索引:type + is_chart + start_date + end_date --- 用于:课程分类明细统计 -ALTER TABLE `courses` -ADD INDEX `idx_type_chart_dates` (`type`, `is_chart`, `start_date`, `end_date`); - --- ============================================ --- 3. calendars 表索引 --- ============================================ - --- 3.1 日期范围查询索引:start_time + end_time --- 用于:getCourseTotal, getCourseDayTotal --- 查询条件:start_time/end_time BETWEEN -ALTER TABLE `calendars` -ADD INDEX `idx_dates_range` (`start_time`, `end_time`); - --- 3.2 课程体系索引:course_type_id + 日期 --- 用于:按课程体系筛选日历 -ALTER TABLE `calendars` -ADD INDEX `idx_course_type_dates` (`course_type_id`, `start_time`, `end_time`); - --- 3.3 统计天数索引:is_count_days + 日期 --- 用于:getCourseDayTotal(开课天数统计) -ALTER TABLE `calendars` -ADD INDEX `idx_count_days_dates` (`is_count_days`, `start_time`, `end_time`); - --- 3.4 课程关联索引:course_id + course_type_id --- 用于:通过 course.type 匹配课程体系 -ALTER TABLE `calendars` -ADD INDEX `idx_course_type_id` (`course_id`, `course_type_id`); - --- ============================================ --- 4. companies 表索引 --- ============================================ - --- 4.1 上市公司索引:company_market --- 用于:shangshi, suzhouStock(上市公司统计) -ALTER TABLE `companies` -ADD INDEX `idx_company_market` (`company_market`); - --- 4.2 被投企业索引:is_yh_invested --- 用于:yhInvestedTotal, companyInvestedYear(被投企业统计) -ALTER TABLE `companies` -ADD INDEX `idx_is_yh_invested` (`is_yh_invested`); - --- 4.3 企业标签索引:company_tag(用于 LIKE 查询) --- 用于:toubuqiye(高新技术企业筛选) --- 注意:LIKE '%高新技术企业%' 无法使用索引,但可以优化前缀匹配 -ALTER TABLE `companies` -ADD INDEX `idx_company_tag` (`company_tag`(100)); - --- 4.4 城市区域索引:company_city + company_area --- 用于:area(区域统计), isSuzhou(苏州筛选) -ALTER TABLE `companies` -ADD INDEX `idx_city_area` (`company_city`, `company_area`); - --- 4.5 地址索引:company_address(用于 LIKE 查询) --- 用于:isSuzhou(苏州筛选) --- 注意:LIKE '%苏州%' 无法使用索引,但可以优化前缀匹配 -ALTER TABLE `companies` -ADD INDEX `idx_company_address` (`company_address`(100)); - --- 4.6 复合索引:company_market + company_city(用于苏州上市公司) -ALTER TABLE `companies` -ADD INDEX `idx_market_city` (`company_market`, `company_city`); - --- ============================================ --- 5. users 表索引 --- ============================================ - --- 5.1 公司关联索引:company_id --- 用于:通过用户关联公司 -ALTER TABLE `users` -ADD INDEX `idx_company_id` (`company_id`); - --- 5.2 跟班学员索引:from(用于 LIKE 查询) --- 用于:genban, ganbu(跟班学员统计) --- 注意:LIKE '%跟班学员%' 无法使用索引,但可以优化前缀匹配 -ALTER TABLE `users` -ADD INDEX `idx_from` (`from`(50)); - --- 5.3 手机号索引:mobile(用于去重) --- 用于:courseSignsTotalByUnique(按手机号去重) -ALTER TABLE `users` -ADD INDEX `idx_mobile` (`mobile`); - --- 5.4 公司名称索引:company_name(用于 LIKE 查询) --- 用于:companyJoin(元和员工筛选) --- 注意:LIKE '%元禾控股%' 等无法使用索引 -ALTER TABLE `users` -ADD INDEX `idx_company_name` (`company_name`(100)); - --- 5.5 用户地址索引:company_address(用于 LIKE 查询) --- 用于:isSuzhou(苏州筛选) -ALTER TABLE `users` -ADD INDEX `idx_user_company_address` (`company_address`(100)); - --- 5.6 复合索引:company_id + from(用于跟班学员筛选) -ALTER TABLE `users` -ADD INDEX `idx_company_from` (`company_id`, `from`(50)); - --- ============================================ --- 6. stock_companies 表索引 --- ============================================ - --- 6.1 上市日期索引:stock_date --- 用于:company_market_year_total(今年上市公司数量) -ALTER TABLE `stock_companys` -ADD INDEX `idx_stock_date` (`stock_date`); - --- 6.2 入学后上市索引:is_after_enrollment --- 用于:company_market_after_enrollment_total -ALTER TABLE `stock_companys` -ADD INDEX `idx_after_enrollment` (`is_after_enrollment`); - --- 6.3 公司关联索引:company_id --- 用于:关联 companies 表 -ALTER TABLE `stock_companys` -ADD INDEX `idx_company_id` (`company_id`); - --- 6.4 复合索引:is_after_enrollment + stock_date -ALTER TABLE `stock_companys` -ADD INDEX `idx_enrollment_date` (`is_after_enrollment`, `stock_date`); - --- ============================================ --- 7. history_courses 表索引 --- ============================================ - --- 7.1 日期范围索引:start_time + end_time --- 用于:历史课程统计 -ALTER TABLE `history_courses` -ADD INDEX `idx_history_dates` (`start_time`, `end_time`); - --- 7.2 课程类型索引:type + 日期 --- 用于:按课程体系筛选历史课程 -ALTER TABLE `history_courses` -ADD INDEX `idx_type_dates` (`type`, `start_time`, `end_time`); - --- 7.3 日历关联索引:calendar_id --- 用于:whereHas('calendar', is_count_people=1) -ALTER TABLE `history_courses` -ADD INDEX `idx_calendar_id` (`calendar_id`); - --- 7.4 复合索引:type + calendar_id + 日期 -ALTER TABLE `history_courses` -ADD INDEX `idx_type_calendar_dates` (`type`, `calendar_id`, `start_time`, `end_time`); - --- ============================================ --- 8. course_types 表索引 --- ============================================ - --- 8.1 历史课程索引:is_history --- 用于:筛选历史课程类型 -ALTER TABLE `course_types` -ADD INDEX `idx_is_history` (`is_history`); - --- 8.2 跟班学员统计索引:is_count_genban --- 用于:genban(筛选需要统计跟班学员的课程) -ALTER TABLE `course_types` -ADD INDEX `idx_is_count_genban` (`is_count_genban`); - --- ============================================ --- 9. 关联查询优化索引 --- ============================================ - --- 9.1 course_signs 关联 users 优化 --- 已通过 user_id 索引优化 - --- 9.2 users 关联 course_signs 优化 --- 需要在 course_signs 表已有 user_id 索引 - --- 9.3 companies 关联 users 优化 --- 需要在 users 表已有 company_id 索引 - --- 9.4 courses 关联 course_types 优化 --- 需要在 courses 表已有 type 索引 - --- ============================================ --- 10. 特殊查询优化建议 --- ============================================ - --- 10.1 JSON 字段查询优化 --- companies.project_users 字段(JSON)无法直接建立索引 --- 建议:如果 investDate 查询频繁,考虑单独建立 invest_dates 表或字段 - --- 10.2 LIKE 查询优化 --- 对于 '%关键词%' 类型的 LIKE 查询,无法使用普通索引 --- 建议: --- 1. 如果可能,改为前缀匹配 '关键词%' 可以使用索引 --- 2. 考虑使用全文索引(FULLTEXT): -ALTER TABLE `companies` -ADD FULLTEXT INDEX `ft_company_tag` (`company_tag`); - -ALTER TABLE `users` -ADD FULLTEXT INDEX `ft_company_name` (`company_name`); - -ALTER TABLE `users` -ADD FULLTEXT INDEX `ft_from` (`from`); - --- 10.3 日期范围查询优化 --- 对于 start_date/end_date BETWEEN 查询,确保日期字段有索引 --- 对于 orWhereBetween 查询,MySQL 可能无法同时使用两个索引 --- 建议:如果性能问题,考虑拆分为两个查询 UNION - --- ============================================ --- 11. 索引使用说明 --- ============================================ - --- 11.1 索引创建顺序 --- 建议按照表的数据量和查询频率,优先创建高频查询的索引 - --- 11.2 索引维护 --- 定期使用 EXPLAIN 分析查询计划,确认索引被正确使用 --- 示例:EXPLAIN SELECT * FROM course_signs WHERE status=1 AND course_id IN (1,2,3); - --- 11.3 索引监控 --- 使用以下查询监控索引使用情况: --- SELECT * FROM sys.schema_unused_indexes; --- SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage; - --- 11.4 注意事项 --- 1. 索引会占用存储空间,增加写入成本 --- 2. 不要过度索引,每个表建议不超过 5-7 个索引 --- 3. 复合索引的顺序很重要,将选择性高的字段放在前面 --- 4. 定期分析表,更新统计信息:ANALYZE TABLE table_name; - --- ============================================ --- 12. 性能优化建议 --- ============================================ - --- 12.1 查询优化 --- 1. 避免在 WHERE 子句中使用函数 --- 2. 使用 EXISTS 替代 IN(当子查询结果集较大时) --- 3. 合理使用 JOIN,避免过度嵌套 - --- 12.2 分页优化 --- 对于大数据量分页,考虑使用游标分页替代 OFFSET - --- 12.3 缓存策略 --- 对于统计类查询,考虑使用 Redis 缓存结果 - --- ============================================ --- 索引创建脚本执行顺序建议 --- ============================================ - --- 1. 先创建核心表索引(course_signs, courses, calendars) --- 2. 再创建关联表索引(companies, users) --- 3. 最后创建辅助表索引(stock_companies, history_courses) - --- 执行前请备份数据库! --- 建议在业务低峰期执行索引创建操作 - diff --git a/统计指标说明.json b/统计指标说明.json deleted file mode 100644 index f460385..0000000 --- a/统计指标说明.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "course_signs_total": { - "name": "报名人数", - "from": "1、统计指定时间范围和课程范围内的所有报名记录数量,包括所有状态的报名(待审核、已通过、已拒绝等);2、加上额外添加的课程数据中的人数统计;3、注意:一个学员报名多个课程会计算多次。", - "verify": "" - }, - "course_signs_pass": { - "name": "培养人次(未去重)", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录数量;2、统计额外添加的课程数据(关联的分类是历史课程,关联的日历是统计人数选项)里满足时间范围内的课程(课程开始或结束时间在时间范围内)培养人数未去重的数据;3、将两部分数据相加得到总数。", - "verify": "报名管理栏目,累加统计对比" - }, - "course_signs_pass_unique": { - "name": "培养人数(已去重)", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录数量按照手机号去重;2、统计额外添加的课程数据(关联的分类是历史课程,关联的日历是统计人数选项)里满足时间范围内的课程(课程开始或结束时间在时间范围内)培养人数去重的数据;3、将两部分数据相加得到总数。", - "verify": "" - }, - "course_total": { - "name": "开课场次", - "from": "1、统计指定时间范围内、指定课程体系范围内的日历里记录数量", - "verify": "日历里的数据导出对比" - }, - "course_day_total": { - "name": "开课天数", - "from": "1、在开课场次的基础上;2、获取设置了统计天数的记录;3、计算天数总和。", - "verify": "日历里的数据导出对比" - }, - "company_market_total": { - "name": "重点上市公司数", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户总数;2、如果一个用户报名多个课程只算一个有效用户;3、筛选出关联的公司是上市公司的用户数量。", - "verify": "企业资质中包含了上市代码的就识别成上市公司" - }, - "ganbu_total": { - "name": "跟班学员数", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户总数;2、如果一个用户报名多个课程只算一个有效用户;3、筛选出有\"跟班学员\"标签的用户总数。", - "verify": "学员中心,学员标签筛选对比" - }, - "company_market_year_total": { - "name": "今年上市公司数量", - "from": "1、从自定义的上市公司数据里;2、统计上市年份在今年的公司数量。", - "verify": "自定义上市公司数据对比" - }, - "company_market_after_enrollment_total": { - "name": "入学后上市公司数量", - "from": "1、直接从上市公司表中查询;2、统计所有标记为\"入学后上市\"的公司数量;3、注意:这个统计不依赖学员报名记录,统计的是自定义的上市的公司。", - "verify": "自定义上市公司数据对比" - }, - "course_signs_invested": { - "name": "累计被投企业数", - "from": "1、指定课程体系下,从2000-01-01到结束日期的所有课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户关联的公司总数;2、筛选被投企业标签的公司;3、再次筛选存在被投日期在时间范围内的公司总数。", - "verify": "" - }, - "company_invested_after_enrollment_total": { - "name": "入学后被投企业数量", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户关联的公司总数;2、筛选被投企业标签的公司;3、再次筛选存在被投日期并且被投日期在用户报名的课程的开始时间之后的公司数量。", - "verify": "" - }, - "company_invested_year_total": { - "name": "今年被投企业数", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户关联的公司总数;2、筛选出有被投企业标签的公司;3、再次筛选存在被投日期并且被投日期是今年的公司数量。", - "verify": "" - }, - "company_join_total": { - "name": "元和员工参与人数", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下报名记录的用户总数(如果一个用户报名多个课程只算一个有效用户);2、筛选自己填写的公司名称包含以下关键词的用户总数:元禾控股、元禾原点、元禾厚望、元禾重元、元禾璞华、元禾谷风、元禾绿柳、元禾辰坤、元禾沙湖、禾裕集团、苏州科服、信诚管理咨询、集成电路公司、常州团队、国器元禾;3、加上自定义的元禾员工/干部培训数据里满足时间范围内的元禾员工总和;4、将两部分数据相加得到总数。", - "verify": "公司管理,筛选公司名字加起来对比" - }, - "company_ganbu_total": { - "name": "全市干部参与企业", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户总数(如果一个用户报名多个课程只算一个有效用户);2、加上\"元禾员工/干部培训\"栏目里满足时间范围内的\"元禾员工参与\"记录的参与数量的总和;3、将两部分数据相加得到总数。", - "verify": "学员中心,学员标签筛选对比" - }, - "cover_head_total": { - "name": "苏州头部企业", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户;2、筛选关联的公司包含\"高新技术企业\"字符串的公司,并且用户填写的公司地址,或者关联的企查查公司地址包含苏州的公司;3、统计公司数量。", - "verify": "" - }, - "cover_rencai_total": { - "name": "苏州高层次人才", - "from": "1、统计指定课程体系下的指定时间范围内的课程(课程开始或结束时间在时间范围内)下审核通过的报名记录的用户;2、筛选满足以下条件之一的用户:参加过\"人才培训\"课程类型的用户,或填写的所有报名表单里个人荣誉字段不包含\"其他\"的用户(剩下的选项是国家人才、市级人才等)。并且用户填写的公司地址,或者关联的企查查公司地址包含苏州的公司;3、取两者的并集得到用户总数。", - "verify": "" - }, - "cover_stock_total": { - "name": "苏州重点上市公司", - "from": "1、统计在指定时间范围内、指定课程范围内报名的学员;2、通过学员关联到其所在公司;3、筛选出上市公司;4、并且用户填写的公司地址,或者关联的企查查公司地址包含苏州的公司", - "verify": "" - } -} \ No newline at end of file