diff --git a/app/Console/Commands/UpdateCompany.php b/app/Console/Commands/UpdateCompany.php index f6eb36b..4cf283e 100755 --- a/app/Console/Commands/UpdateCompany.php +++ b/app/Console/Commands/UpdateCompany.php @@ -43,8 +43,8 @@ class UpdateCompany extends Command public function handle() { $user_id = $this->option('user_id'); - $updateLocal = (int)$this->option('address'); - $updateMarket = (int)$this->option('market'); + $updateLocal = (int) $this->option('address'); + $updateMarket = (int) $this->option('market'); // 更新公司信息 $this->compnay($user_id); // 更新经纬度信息(可选) @@ -227,11 +227,11 @@ class UpdateCompany extends Command $bar->start(); // 上市代码正则:匹配全球各地上市公司股票代码后缀 - // 支持的后缀:.SH,.SZ,.BJ,.TW,.HK,.SG,.US,.DE,.FR,.JP,.KR,.N,.O,.A,.PK,.Q,.TO,.AX,.L,-S,-B,-SB,-P,-Z,-W,-SW,-SWR,-R,-WR,-X,-SS,-RS,.WS,.U,.PR,.B,.DB,.UN,.RT,.WT,.E,.C,.D,.F,.G,.H,.I,.J,.K,.L,.M,.N,.O,.P,.V,.Y,.Z - // 简化匹配:只要字符串中包含分隔符(.或-)+指定后缀,就算上市 + // 支持的后缀:.SH,.SZ,.BJ,.TW,.HK,.SG,.US,.DE,.FR,.JP,.KR,.N,.O,.A,.PK,.Q,.TO,.AX,.L,.WS,.U,.PR,.B,.DB,.UN,.RT,.WT,.E,.C,.D,.F,.G,.H,.I,.J,.K,.L,.M,.N,.O,.P,.V,.Y,.Z + // 简化匹配:只要字符串中包含分隔符(.)+指定后缀,就算上市(只匹配.开头的,不匹配-开头的) // 支持格式:688001.SH、AAPG.O、TSLA.US、华兴源创(688001.SH)等 // 后缀按长度从长到短排序,避免短后缀误匹配 - $stockCodePattern = '/[.\-](SWR|SW|WR|SS|RS|SB|PK|TO|AX|WS|PR|DB|UN|RT|WT|SH|SZ|BJ|TW|HK|SG|US|DE|FR|JP|KR|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|U|V|W|X|Y|Z)(?![A-Za-z0-9])/i'; + $stockCodePattern = '/\.(SWR|SW|WR|SS|RS|SB|PK|TO|AX|WS|PR|DB|UN|RT|WT|SH|SZ|BJ|TW|HK|SG|US|DE|FR|JP|KR|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|U|V|W|X|Y|Z)(?![A-Za-z0-9])/i'; $updatedCount = 0; foreach ($companies as $company) { diff --git a/app/Http/Controllers/Admin/CompanyController.php b/app/Http/Controllers/Admin/CompanyController.php index bd0a774..216210f 100644 --- a/app/Http/Controllers/Admin/CompanyController.php +++ b/app/Http/Controllers/Admin/CompanyController.php @@ -11,6 +11,8 @@ use App\Models\Calendar; use App\Models\Company; use App\Models\CourseContentEvaluationAsk; use App\Models\CourseContentEvaluationForm; +use App\Models\CourseSign; +use App\Models\CourseType; use App\Models\CustomForm; use App\Models\CustomFormField; use App\Models\EmailTemplate; @@ -37,6 +39,8 @@ class CompanyController extends BaseController * tags={"公司管理"}, * summary="参数", * description="", + * @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string"), required=false, description="开始日期"), + * @OA\Parameter(name="end_date", 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( * response="200", @@ -46,6 +50,9 @@ class CompanyController extends BaseController */ public function config() { + // 如果提供了开始时间和结束时间,则计算统计 + $start_date = request('start_date', CourseType::START_DATE); + $end_date = request('end_date', date('Y-m-d')); // 企业标签 $companiesTags = Company::where('company_tag', '!=', '')->pluck('company_tag'); $companiesTags = $companiesTags->flatten()->implode(','); @@ -56,7 +63,17 @@ class CompanyController extends BaseController }); $companiesTags = array_values($companiesTags); - return $this->success(compact('companiesTags')); + // 累计被投企业数(从起始日期到结束日期) + $course_signs_invested = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, null); + + // 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) + $company_invested_after_enrollment_total = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, null); + + // 今年范围内被投企业数(在指定时间范围内报名的学员所在公司中,被投时间在年份范围内的公司数量) + $company_invested_year_total = CourseSign::companyInvestedYear($start_date, $end_date, null); + + + return $this->success(compact('companiesTags', 'course_signs_invested', 'company_invested_after_enrollment_total', 'company_invested_year_total')); } @@ -88,11 +105,13 @@ class CompanyController extends BaseController public function index() { $all = request()->all(); - $list = $this->model->with(['users' => function ($query) use ($all) { - $query->whereHas('courseSigns', function ($q) { - $q->where('status', 1); - })->with('courseSigns.course'); - }])->whereHas('users', function ($query) use ($all) { + $list = $this->model->with([ + 'users' => function ($query) use ($all) { + $query->whereHas('courseSigns', function ($q) { + $q->where('status', 1); + })->with('courseSigns.course'); + } + ])->whereHas('users', function ($query) use ($all) { if (isset($all['course_type_id'])) { $query->whereHas('courses', function ($q) use ($all) { $q->where('type', $all['course_type_id']); @@ -211,11 +230,13 @@ class CompanyController extends BaseController if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } - $detail = $this->model->with(['users' => function ($query) { - $query->whereHas('courseSigns', function ($q) { - $q->where('status', 1); - })->with('courseSigns.course'); - }])->find($all['id']); + $detail = $this->model->with([ + 'users' => function ($query) { + $query->whereHas('courseSigns', function ($q) { + $q->where('status', 1); + })->with('courseSigns.course'); + } + ])->find($all['id']); return $this->success($detail); } diff --git a/app/Http/Controllers/Admin/EmployeeParticipationController.php b/app/Http/Controllers/Admin/EmployeeParticipationController.php new file mode 100644 index 0000000..c57fda6 --- /dev/null +++ b/app/Http/Controllers/Admin/EmployeeParticipationController.php @@ -0,0 +1,209 @@ +all(); + $list = $this->model->where(function ($query) use ($all) { + 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 . '%'); + } + // 范围搜索 + if ($op == 'range') { + list($from, $to) = explode(',', $value); + if (empty($from) || empty($to)) { + continue; + } + $query->whereBetween($key, [$from, $to]); + } + } + } + })->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'] ?? []; + // 导出文件名字 + $tableName = $this->model->getTable(); + $filename = (new CustomForm())->getTableComment($tableName); + return Excel::download(new BaseExport($export_fields, $list, $tableName), $filename . date('YmdHis') . '.xlsx'); + } else { + // 输出 + $list = $list->paginate($all['page_size'] ?? 20); + } + return $this->success($list); + } + + /** + * @OA\Get( + * path="/api/admin/employee-participations/show", + * tags={"员工参与"}, + * summary="详情", + * description="", + * @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"), + * @OA\Parameter(name="show_relation", 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( + * response="200", + * description="暂无" + * ) + * ) + */ + public function show() + { + $all = \request()->all(); + $messages = [ + 'id.required' => 'Id必填', + ]; + $validator = Validator::make($all, [ + 'id' => 'required' + ], $messages); + if ($validator->fails()) { + return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); + } + $detail = $this->model->find($all['id']); + return $this->success($detail); + } + + /** + * @OA\Post( + * path="/api/admin/employee-participations/save", + * tags={"员工参与"}, + * summary="保存", + * description="", + * @OA\Parameter(name="id", in="query", @OA\Schema(type="int"), required=false, description="Id(存在更新,不存在新增)"), + * @OA\Parameter(name="type", in="query", @OA\Schema(type="integer"), required=true, description="类型:1员工参与数,2干部培训数"), + * @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string", format="date"), required=false, description="开始日期"), + * @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_name", in="query", @OA\Schema(type="string", nullable=true), description="课程名称"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), + * @OA\Response( + * response="200", + * description="操作成功" + * ) + * ) + */ + public function save() + { + $all = \request()->all(); + DB::beginTransaction(); + try { + if (isset($all['id'])) { + $model = $this->model->find($all['id']); + if (empty($model)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']); + } + } else { + $model = $this->model; + } + $model->fill($all); + $model->save(); + DB::commit(); + return $this->success($model); + } catch (\Exception $exception) { + DB::rollBack(); + return $this->fail([$exception->getCode(), $exception->getMessage()]); + } + } + + /** + * @OA\Get( + * path="/api/admin/employee-participations/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/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index 08037af..ba42a4d 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -221,8 +221,7 @@ class OtherController extends CommonController $end_date = $params['end_date']; $course_type_id = $params['course_type_id']; $courses = $params['courses']; - // 被投企业数 - $list['course_signs_invested'] = CourseSign::yhInvested($start_date, $end_date, $courses->pluck('id')->toArray()); + // 报名人数 $list['course_signs_total'] = CourseSign::courseSignsTotal($start_date, $end_date, null, $courses->pluck('id')); // 审核通过人数 @@ -230,16 +229,33 @@ class OtherController extends CommonController // 审核通过人数去重 $list['course_signs_pass_unique'] = CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses->pluck('id'), null); // 开课场次 + // 开课场次 $calendar = Calendar::where(function ($query) use ($start_date, $end_date) { $query->whereBetween('start_time', [$start_date, $end_date]) ->orWhereBetween('end_time', [$start_date, $end_date]); - })->where(function ($query) { - $course_type_id = request('course_type_id'); + })->where(function ($query) use ($course_type_id) { + // 条件1:有 course_id 的数据,通过 course.type 匹配课程体系 + // 条件2:没有 course_id 的数据,直接用 course_type_id 字段匹配 + // 两个条件是或关系 + if ($course_type_id) { - $course_type_id = explode(',', $course_type_id); - $query->whereIn('course_type_id', $course_type_id); + $course_type_id_array = is_array($course_type_id) ? $course_type_id : explode(',', $course_type_id); + + // 条件1:有 course_id 时,通过关联的 course.type 匹配 + $query->where(function ($q) use ($course_type_id_array) { + + $q->whereHas('course', function ($subQ) use ($course_type_id_array) { + $subQ->whereIn('type', $course_type_id_array); + }); + }); + + // 条件2:没有 course_id 时,直接用 course_type_id 字段匹配(或关系) + $query->orWhere(function ($q) use ($course_type_id_array) { + $q->whereIn('course_type_id', $course_type_id_array); + }); } - }); + })->get(); + $list['course_total'] = (clone $calendar)->count(); // 开课天数 $list['course_day_total'] = (clone $calendar)->where('is_count_days', 1)->sum('days'); @@ -258,9 +274,15 @@ class OtherController extends CommonController // 入学后上市公司数量(在指定时间范围内报名的学员所在公司中,在入学后上市的公司数量) $list['company_market_after_enrollment_total'] = CourseSign::companyMarketAfterEnrollment($start_date, $end_date, $course_ids); + // 累计被投企业数 + $list['course_signs_invested'] = CourseSign::yhInvestedTotal(CourseType::START_DATE, $end_date, $course_ids); + // 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) $list['company_invested_after_enrollment_total'] = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids); + // 今年被投企业数 + $list['company_invested_year_total'] = CourseSign::companyInvestedYear($start_date, $end_date, $course_ids); + // 元和员工参与人数 $list['company_join_total'] = CourseSign::companyJoin($start_date, $end_date, $course_ids); // 全市干部参与企业 @@ -290,11 +312,11 @@ class OtherController extends CommonController $courseTypesSum[] = [ 'course_type' => $courseType->name, // 培养人数 - 'course_type_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, $courses2->pluck('id')), + 'course_type_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, $courses2->pluck('id'), false, false), // 去重培养人数 - 'course_type_signs_pass_unique' => CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses2->pluck('id'), null), + 'course_type_signs_pass_unique' => CourseSign::courseSignsTotalByUnique($start_date, $end_date, 1, $courses2->pluck('id'), false, false), 'course_name' => $course->name, - 'course_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, [$course->id]), + 'course_signs_pass' => CourseSign::courseSignsTotal($start_date, $end_date, 1, [$course->id], false, false), // 跟班学员数量 'genban_total' => CourseSign::genban($start_date, $end_date, [$course->id]), // 被投企业数 @@ -305,29 +327,33 @@ class OtherController extends CommonController } } // 附加历史课程数据 - $historyCourses = HistoryCourse::whereHas('calendar', function ($query) { - $query->where('is_count_people', 1); - })->where(function ($query) use ($start_date, $end_date) { - // 开始结束日期的筛选。or查询 - $query->whereBetween('start_time', [$start_date, $end_date]) - ->orWhereBetween('end_time', [$start_date, $end_date]); - })->where(function ($query) { - $course_type_id = request('course_type_id'); - if ($course_type_id) { - $course_type_id = explode(',', $course_type_id); - $query->whereIn('type', $course_type_id); + $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get(); + foreach ($courseTypesHistory as $historyCourse) { + $courses3 = HistoryCourse::whereHas('calendar', function ($query) { + $query->where('is_count_people', 1); + })->where(function ($query) use ($start_date, $end_date) { + // 开始结束日期的筛选。or查询 + $query->whereBetween('start_time', [$start_date, $end_date]) + ->orWhereBetween('end_time', [$start_date, $end_date]); + })->where('type', $historyCourse->id)->get(); + foreach ($courses3 as $course) { + $courseTypesSum[] = [ + 'course_type' => $historyCourse->name, + 'course_name' => $course->course_name, + // 培养人数 + 'course_type_signs_pass' => $course->course_type_signs_pass, + // 去重培养人数 + 'course_type_signs_pass_unique' => $course->course_type_signs_pass_unique, + // 课程人数 + 'course_signs_pass' => $course->course_signs_pass, + // 跟班学员数量 + 'genban_total' => 0, + // 被投企业数 + 'yh_invested_total' => 0, + // 元禾同事数 + 'company_join_total' => 0, + ]; } - })->get(); - foreach ($historyCourses as $historyCourse) { - $courseTypesSum[] = [ - 'course_type' => $historyCourse->typeDetail->name, - // 培养人数 - 'course_type_signs_pass' => $historyCourses->where('type', $historyCourse->type)->sum('course_type_signs_pass'), - // 去重培养人数 - 'course_type_signs_pass_unique' => $historyCourses->where('type', $historyCourse->type)->sum('course_type_signs_pass_unique'), - 'course_name' => $historyCourse->course_name, - 'course_signs_pass' => $historyCourse->course_signs_pass, - ]; } // 区域明细统计 @@ -341,7 +367,7 @@ class OtherController extends CommonController * tags={"其他"}, * summary="课程统计明细导出", * description="导出课程统计数据的明细", - * @OA\Parameter(name="export_type", in="query", @OA\Schema(type="string"), required=true, description="导出类型:course_signs_invested-被投企业明细, course_signs_total-报名人数明细, course_signs_pass-审核通过人数明细, course_signs_pass_unique-审核通过人数去重明细, courseTypesSum-课程分类明细, areas-区域明细, company_market_total-上市公司明细, ganbu_total-跟班学员明细, company_market_year_total-今年上市公司明细, company_market_after_enrollment_total-入学后上市公司明细, company_invested_after_enrollment_total-入学后被投企业明细, course_total-开课场次明细, course_day_total-开课天数明细, company_join_total-元和员工参与企业明细, company_ganbu_total-全市干部参与企业明细, cover_head_total-苏州头部企业明细, cover_rencai_total-高层次人才明细, cover_stock_total-重点上市公司明细"), + * @OA\Parameter(name="export_type", in="query", @OA\Schema(type="string"), required=true, description="导出类型:course_signs_invested-被投企业明细, course_signs_total-报名人数明细, course_signs_pass-审核通过人数明细, course_signs_pass_unique-审核通过人数去重明细, courseTypesSum-课程分类明细, areas-区域明细, company_market_total-上市公司明细, ganbu_total-跟班学员明细, company_market_year_total-今年上市公司明细, company_market_after_enrollment_total-入学后上市公司明细, company_invested_after_enrollment_total-入学后被投企业明细, company_invested_year_total-年份范围内被投企业明细, course_total-开课场次明细, course_day_total-开课天数明细, company_join_total-元和员工参与企业明细, company_ganbu_total-全市干部参与企业明细, cover_head_total-苏州头部企业明细, cover_rencai_total-高层次人才明细, cover_stock_total-重点上市公司明细"), * @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string"), required=false, description="开始日期"), * @OA\Parameter(name="end_date", in="query", @OA\Schema(type="string"), required=false, description="结束日期"), * @OA\Parameter(name="course_type_id", in="query", @OA\Schema(type="string"), required=false, description="课程体系id,多个英文逗号"), @@ -374,7 +400,7 @@ class OtherController extends CommonController switch ($export_type) { case 'course_signs_invested': // 被投企业明细 - 使用与coursesHome相同的算法 - $companies = CourseSign::yhInvested($start_date, $end_date, $course_ids, true); + $companies = CourseSign::yhInvestedTotal($start_date, $end_date, $course_ids, true); foreach ($companies as $company) { $data[] = [ 'company_name' => $company->company_name, @@ -830,6 +856,92 @@ class OtherController extends CommonController $filename = '入学后被投企业明细'; break; + case 'company_invested_year_total': + // 年份范围内被投企业明细 - 所有年份范围内被投企业,关联学员、课程信息 + // 数据结构:主表是公司,子数据是学员信息 + // 导出时:公司信息只在第一行显示,后续行公司信息为空 + $companiesData = CourseSign::companyInvestedYear($start_date, $end_date, $course_ids->toArray(), true); + + foreach ($companiesData as $item) { + $company = $item['company']; + $users = $item['users'] ?? []; + + // 公司基本信息(只在第一行使用) + $companyInfo = [ + 'company_name' => $company->company_name, + 'company_legal_representative' => $company->company_legal_representative ?? '', + 'company_date' => $company->company_date ?? '', + 'company_address' => $company->company_address ?? '', + 'company_city' => $company->company_city ?? '', + 'company_area' => $company->company_area ?? '', + 'company_tag' => $company->company_tag ?? '', + ]; + + if (empty($users)) { + // 如果没有学员报名记录,仍然导出公司基本信息 + $data[] = array_merge($companyInfo, [ + 'user_name' => '', + 'course_name' => '', + 'course_type' => '', + ]); + } else { + // 每个学员一行,多个课程合并显示 + $isFirstRow = true; + foreach ($users as $userInfo) { + $courses = $userInfo['courses'] ?? []; + + // 合并同一学员的多个课程:格式为"课程体系-课程名称,课程体系-课程名称" + $courseList = []; + foreach ($courses as $courseInfo) { + $courseType = $courseInfo['course_type'] ?? ''; + $courseName = $courseInfo['course_name'] ?? ''; + if ($courseType && $courseName) { + $courseList[] = $courseType . '-' . $courseName; + } elseif ($courseName) { + $courseList[] = $courseName; + } + } + $courseDisplay = implode("\r\n", $courseList); + + if ($isFirstRow) { + // 第一行:显示公司信息 + $data[] = array_merge($companyInfo, [ + 'user_name' => $userInfo['user_name'] ?? '', + 'course_name' => $courseDisplay, + 'course_type' => '', // 课程类型已合并到课程名称中 + ]); + $isFirstRow = false; + } else { + // 后续行:公司信息为空 + $data[] = [ + 'company_name' => '', + 'company_legal_representative' => '', + 'company_date' => '', + 'company_address' => '', + 'company_city' => '', + 'company_area' => '', + 'company_tag' => '', + 'user_name' => $userInfo['user_name'] ?? '', + 'course_name' => $courseDisplay, + ]; + } + } + } + } + $fields = [ + 'company_name' => '企业名称', + 'company_legal_representative' => '法人', + 'company_date' => '成立时间', + 'company_address' => '地址', + 'company_city' => '所在城市', + 'company_area' => '所在区域', + 'company_tag' => '企业资质', + 'user_name' => '学员姓名', + 'course_name' => '课程信息', + ]; + $filename = '年份范围内被投企业明细'; + break; + case 'course_total': // 开课场次明细 - 与coursesHome算法一致 $calendars = Calendar::whereBetween('date', [$start_date, $end_date]) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 7d2ac42..bc0bdc0 100755 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -210,10 +210,10 @@ class UserController extends BaseController $query->with('course.typeDetail')->orderBy('fee_status', 'desc'); } ])->withCount([ - 'appointments' => function ($query) { - $query->whereIn('status', [0, 1]); - } - ]); + 'appointments' => function ($query) { + $query->whereIn('status', [0, 1]); + } + ]); // 是否被投企业 if (isset($all['is_yh_invested'])) { $list = $list->whereHas('company', function ($query) use ($all) { @@ -287,7 +287,7 @@ class UserController extends BaseController } $list = $list->where(function ($query) use ($all) { if (isset($all['from'])) { - $query->where('from', $all['from']); + $query->where('from', 'like', '%' . $all['from'] . '%'); } if (isset($all['is_vip'])) { $query->where('is_vip', $all['is_vip']); @@ -318,9 +318,7 @@ class UserController extends BaseController $query->where('name', 'like', '%' . $all['name'] . '%'); } if (isset($all['company_name'])) { - $query->whereHas('company', function ($query) use ($all) { - $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); - }); + $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); } if (isset($all['company_position'])) { $query->where('company_position', $all['company_position']); @@ -540,8 +538,7 @@ class UserController extends BaseController } else { if (in_array($k, ['company_type', 'type'])) { $list[$key][$k] = str_replace('、', ',', $value[$v]); - $list[$key][$k] = str_replace(',', ',', $list[$key][$k]); - ; + $list[$key][$k] = str_replace(',', ',', $list[$key][$k]);; } else { $list[$key][$k] = $value[$v]; } diff --git a/app/Models/CourseSign.php b/app/Models/CourseSign.php index 8faebf9..2683f4f 100755 --- a/app/Models/CourseSign.php +++ b/app/Models/CourseSign.php @@ -100,7 +100,7 @@ class CourseSign extends SoftDeletesModel /** * 指定时间内的报名信息(未去重) */ - public static function courseSignsTotal($start_date, $end_date, $status = null, $course_ids = null, $retList = false) + public static function courseSignsTotal($start_date, $end_date, $status = null, $course_ids = null, $retList = false, $needHistory = true) { $totalQuery = self::getStudentList($start_date, $end_date, $status, $course_ids); if ($retList) { @@ -110,19 +110,24 @@ class CourseSign extends SoftDeletesModel // 基础数据 $baseTotal = $totalQuery->count(); // 历史数据 - $historyTotal = HistoryCourse::whereHas('calendar', function ($query) { - $query->where('is_count_people', 1); - })->where(function ($query) use ($start_date, $end_date) { - // 开始结束日期的筛选。or查询 - $query->whereBetween('start_time', [$start_date, $end_date]) - ->orWhereBetween('end_time', [$start_date, $end_date]); - })->where(function ($query) { - $course_type_id = request('course_type_id'); - if ($course_type_id) { - $course_type_id = explode(',', $course_type_id); - $query->whereIn('type', $course_type_id); - } - })->sum('course_type_signs_pass'); + $historyTotal = 0; + if ($needHistory) { + $historyTotal = HistoryCourse::whereHas('calendar', function ($query) { + $query->where('is_count_people', 1); + })->whereHas('typeDetail', function ($query) { + $query->where('is_history', 1); + })->where(function ($query) use ($start_date, $end_date) { + // 开始结束日期的筛选。or查询 + $query->whereBetween('start_time', [$start_date, $end_date]) + ->orWhereBetween('end_time', [$start_date, $end_date]); + })->where(function ($query) { + $course_type_id = request('course_type_id'); + if ($course_type_id) { + $course_type_id = explode(',', $course_type_id); + $query->whereIn('type', $course_type_id); + } + })->sum('course_type_signs_pass'); + } // 返回统计数据 return $historyTotal + $baseTotal; } @@ -131,7 +136,7 @@ class CourseSign extends SoftDeletesModel /** * 指定时间内的报名信息(去重) */ - public static function courseSignsTotalByUnique($start_date, $end_date, $status = null, $course_ids = null, $retList = false) + public static function courseSignsTotalByUnique($start_date, $end_date, $status = null, $course_ids = null, $retList = false, $needHistory = true) { $totalQuery = self::getStudentList($start_date, $end_date, $status, $course_ids); $user = User::whereIn('id', $totalQuery->get()->pluck('user_id'))->groupBy('mobile')->get(); @@ -140,25 +145,82 @@ class CourseSign extends SoftDeletesModel return $user; } else { $baseTotal = $user->count(); - // 历史数据 - $historyTotal = HistoryCourse::whereHas('calendar', function ($query) { - $query->where('is_count_people', 1); - })->where(function ($query) use ($start_date, $end_date) { - // 开始结束日期的筛选。or查询 - $query->whereBetween('start_time', [$start_date, $end_date]) - ->orWhereBetween('end_time', [$start_date, $end_date]); - })->where(function ($query) { - $course_type_id = request('course_type_id'); - if ($course_type_id) { - $course_type_id = explode(',', $course_type_id); - $query->whereIn('type', $course_type_id); - } - })->sum('course_type_signs_pass_unique'); + $historyTotal = 0; + if ($needHistory) { + // 历史数据 + $historyTotal = HistoryCourse::whereHas('calendar', function ($query) { + $query->where('is_count_people', 1); + })->whereHas('typeDetail', function ($query) { + $query->where('is_history', 1); + })->where(function ($query) use ($start_date, $end_date) { + // 开始结束日期的筛选。or查询 + $query->whereBetween('start_time', [$start_date, $end_date]) + ->orWhereBetween('end_time', [$start_date, $end_date]); + })->where(function ($query) { + $course_type_id = request('course_type_id'); + if ($course_type_id) { + $course_type_id = explode(',', $course_type_id); + $query->whereIn('type', $course_type_id); + } + })->sum('course_type_signs_pass_unique'); + } // 统计数据 return $baseTotal + $historyTotal; } } + /** + * 累计被投企业统计 + * @param string|null $start_date 开始日期 + * @param string|null $end_date 结束日期 + * @param array|null $course_ids 课程ID(仅在自定义时间时生效) + * @param bool $retList 是否返回列表 + */ + public static function yhInvestedTotal($start_date = null, $end_date = null, $course_ids, $retList = false) + { + // 默认时间:获取所有学员,不限制课程 + $userIds = self::getStudentList($start_date, $end_date, 1, $course_ids)->get()->pluck('user_id'); + + // 获取这些学员所在的被投企业 + $companies = Company::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) { + return $companies->values(); + } else { + return $companies->count(); + } + } + /** * 被投企业统计 * @param string|null $start_date 开始日期 @@ -183,12 +245,10 @@ class CourseSign extends SoftDeletesModel $companies = Company::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) { @@ -213,6 +273,123 @@ class CourseSign extends SoftDeletesModel } } + /** + * 被投企业统计(统计或列表)- 按年份范围统计 + * @param string|null $start_date 开始日期 + * @param string|null $end_date 结束日期 + * @param array|null $course_ids 课程ID数组,不传则统计所有课程 + * @param bool $retList 是否返回列表,false返回数量,true返回列表(包含学员、课程信息) + * @return int|array + */ + public static function companyInvestedYear($start_date = null, $end_date = null, $course_ids = null, $retList = false) + { + $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); + $courseSigns = $courseSignsQuery->with(['user.company', 'course.typeDetail'])->get(); + + // 获取所有被投企业的ID + $companyIds = $courseSigns->pluck('user.company.id') + ->filter() + ->unique() + ->toArray(); + + // 计算年份范围 + $years = []; + if ($start_date && $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'); + } + + // 获取这些公司中标记为被投的公司 + $allInvestedCompanies = Company::whereIn('id', $companyIds) + ->where('is_yh_invested', 1) + ->get(); + + // 筛选出被投时间在年份范围内的企业 + $companies = []; + foreach ($allInvestedCompanies as $company) { + $projectUsers = $company->project_users ?? []; + $hasInvestInYears = false; + foreach ($projectUsers as $item) { + $investDate = $item['investDate'] ?? null; + if ($investDate) { + $investYear = (int)date('Y', strtotime($investDate)); + if (in_array($investYear, $years)) { + $hasInvestInYears = true; + break; + } + } + } + if ($hasInvestInYears) { + $companies[$company->id] = $company; + } + } + $companies = collect($companies); + + if ($retList) { + // 返回详细列表:主表是公司,子数据是学员信息 + $result = []; + foreach ($courseSigns as $courseSign) { + if (!$courseSign->user || !$courseSign->user->company) { + continue; + } + + $companyId = $courseSign->user->company->id; + // 只处理年份范围内被投企业的记录 + if (!isset($companies[$companyId])) { + continue; + } + + $company = $companies[$companyId]; + + // 如果公司还没有在结果中,初始化 + if (!isset($result[$companyId])) { + $result[$companyId] = [ + 'company' => $company, + 'users' => [], + ]; + } + + // 按学员分组,收集每个学员的课程信息 + $userId = $courseSign->user->id; + if (!isset($result[$companyId]['users'][$userId])) { + $result[$companyId]['users'][$userId] = [ + 'user' => $courseSign->user, + 'user_name' => $courseSign->user->name ?? '', + 'mobile' => $courseSign->user->mobile ?? '', + 'courses' => [], + ]; + } + + // 添加该学员的课程信息 + $result[$companyId]['users'][$userId]['courses'][] = [ + 'course_name' => $courseSign->course->name ?? '', + 'course_type' => $courseSign->course->typeDetail->name ?? '', + 'course_sign' => $courseSign, + ]; + } + + // 将 users 转换为数组(去掉 user_id 作为 key) + foreach ($result as $companyId => $item) { + $result[$companyId]['users'] = array_values($item['users']); + } + + // 转换为数组并返回 + return array_values($result); + } else { + // 返回统计数据 + return $companies->count(); + } + } + + /** * 跟班学员(统计或列表) * @param string $start_date 开始日期 @@ -408,13 +585,8 @@ class CourseSign extends SoftDeletesModel '信诚管理咨询', '集成电路公司', '常州团队', - '国企元禾' + '国器元禾' ]; - // $company = Company::where(function ($query) use ($companyNameKeyword) { -// foreach ($companyNameKeyword as $item) { -// $query->orWhere('company_name', 'like', '%' . $item . '%'); -// } -// })->get(); $list = User::whereIn('id', $courseSignByType->pluck('user_id')) ->where(function ($query) use ($companyNameKeyword) { foreach ($companyNameKeyword as $item) { @@ -442,9 +614,15 @@ class CourseSign extends SoftDeletesModel public static function ganbu($start_date = null, $end_date = null, $course_ids = null, $retList = false) { $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); + // 获取需要统计跟班学员的课程 + $genbanCourse = Course::whereHas('typeDetail', function ($query) { + $query->where('is_count_genban', 1); + })->get(); + $courseSigns = $courseSignsQuery->whereHas('user', function ($query) { $query->where('from', 'like', '%跟班学员%'); - })->get(); + })->whereIn('course_id', $genbanCourse->pluck('id'))->get(); + if ($retList) { return User::with('company')->whereIn('id', $courseSigns->pluck('user_id'))->get(); } else { @@ -684,3 +862,4 @@ class CourseSign extends SoftDeletesModel } + diff --git a/app/Models/CourseType.php b/app/Models/CourseType.php index 25aaf67..9354b6f 100755 --- a/app/Models/CourseType.php +++ b/app/Models/CourseType.php @@ -6,7 +6,7 @@ namespace App\Models; class CourseType extends SoftDeletesModel { - const START_DATE = '2020-01-01 00:00:00'; + const START_DATE = '2000-01-01'; protected $appends = ['is_count_genban_text']; diff --git a/app/Models/EmployeeParticipation.php b/app/Models/EmployeeParticipation.php new file mode 100644 index 0000000..42367cc --- /dev/null +++ b/app/Models/EmployeeParticipation.php @@ -0,0 +1,34 @@ + [ + 1 => '员工参与数', + 2 => '干部培训数', + ], + ]; + + /** + * 获取类型文本 + */ + public function getTypeTextAttribute() + { + return self::$intToString['type'][$this->type] ?? ''; + } + + /** + * 关联课程类型 + */ + public function courseType() + { + return $this->hasOne(CourseType::class, 'id', 'course_type_id'); + } +} + diff --git a/database/migrations/2025_11_29_112835_create_employee_participations_table.php b/database/migrations/2025_11_29_112835_create_employee_participations_table.php new file mode 100644 index 0000000..47ec5b9 --- /dev/null +++ b/database/migrations/2025_11_29_112835_create_employee_participations_table.php @@ -0,0 +1,39 @@ +comment('员工参与表'); + $table->increments('id'); + $table->tinyInteger('type')->nullable()->comment('类型:1员工参与数,2干部培训数'); + $table->date('start_date')->nullable()->comment('开始日期'); + $table->date('end_date')->nullable()->comment('结束日期'); + $table->integer('total')->nullable()->comment('数量'); + $table->integer('course_type_id')->nullable()->comment('课程类型ID'); + $table->string('course_name')->nullable()->comment('课程名称'); + $table->dateTime('created_at')->nullable(); + $table->dateTime('updated_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('employee_participations'); + } +}; + diff --git a/routes/api.php b/routes/api.php index 8f25459..b8bf19f 100755 --- a/routes/api.php +++ b/routes/api.php @@ -263,6 +263,11 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () { Route::post('history-courses/save', [\App\Http\Controllers\Admin\HistoryCourseController::class, "save"]); Route::get('history-courses/destroy', [\App\Http\Controllers\Admin\HistoryCourseController::class, "destroy"]); + // 员工参与 + Route::get('employee-participations/index', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "index"]); + Route::get('employee-participations/show', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "show"]); + Route::post('employee-participations/save', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "save"]); + Route::get('employee-participations/destroy', [\App\Http\Controllers\Admin\EmployeeParticipationController::class, "destroy"]); // 统计数据配置管理 Route::get('statistics-configs/index', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "index"]); diff --git a/user_statistics_config_json结构说明.md b/user_statistics_config_json结构说明.md deleted file mode 100644 index a42822e..0000000 --- a/user_statistics_config_json结构说明.md +++ /dev/null @@ -1,550 +0,0 @@ -# 用户统计数据配置 JSON 结构说明 - -## 概述 - -`user_statistics_configs` 表的 `config_json` 字段用于存储动态统计配置,包含三个主要部分:数据来源、条件设置、统计方式。 - -## JSON 结构 - -```json -{ - "data_source": { - "main_model": "user|company|course_sign|course|course_type", - "relations": ["user", "company", "course_sign", "course", "course_type"] - }, - "conditions": { - "logic": "and|or", - "items": [ - { - "key": "字段名", - "operator": "操作类型", - "value": "值" - } - ] - }, - "statistics": { - "type": "sum|max|min|count|count_distinct", - "field": "统计字段(sum/max/min 时使用,可选)", - "distinct_field": "去重字段(count_distinct 时使用,可选)", - "group_by": "分组字段(可选,不设置则不分组)", - "order_by": { - "field": "排序字段(可选)", - "direction": "asc|desc" - } - } -} -``` - ---- - -## 一、数据来源(data_source) - -### 1.1 主模型(main_model) - -**说明**:指定统计数据的主要来源模型。 - -**可选值**: -- `user` - 用户模型 -- `company` - 公司模型 -- `course_sign` - 报名模型 -- `course` - 课程模型 -- `course_type` - 课程分类模型 - -**示例**: -```json -{ - "main_model": "user" -} -``` - -### 1.2 关联模型(relations) - -**说明**:指定需要关联的其他模型,可以关联多个模型。 - -**可选值**(数组): -- `user` - 用户模型 -- `company` - 公司模型 -- `course_sign` - 报名模型 -- `course` - 课程模型 -- `course_type` - 课程分类模型 - -**注意**: -- 关联模型不能包含主模型本身 -- 可以关联多个模型 -- 数组可以为空 - -**示例**: -```json -{ - "relations": ["company", "course_sign"] -} -``` - ---- - -## 二、条件设置(conditions) - -### 2.1 逻辑关系(logic) - -**说明**:指定多个条件之间的逻辑关系。 - -**可选值**: -- `and` - 所有条件都必须满足(AND) -- `or` - 至少一个条件满足(OR) - -**示例**: -```json -{ - "logic": "and" -} -``` - -### 2.2 条件项(items) - -**说明**:条件数组,每个条件包含键名、操作类型和值。 - -**条件项结构**: -```json -{ - "key": "字段名", - "operator": "操作类型", - "value": "值" -} -``` - -#### 字段说明 - -- **key**(字符串):要查询的字段名 - - 可以是主模型的字段 - - 可以是关联模型的字段(使用点号分隔,如 `company.name`) - -- **operator**(字符串):操作类型 - - `eq` - 等于 - - `neq` - 不等于 - - `gt` - 大于 - - `egt` - 大于等于 - - `lt` - 小于 - - `elt` - 小于等于 - - `like` - 模糊匹配 - - `notlike` - 不匹配 - - `in` - 在范围内(值为逗号分隔的字符串) - - `notin` - 不在范围内 - - `between` - 在范围内(值为逗号分隔的两个值) - - `notbetween` - 不在范围内 - - `isnull` - 为空(value 可省略) - - `isnotnull` - 不为空(value 可省略) - -- **value**(字符串/数字/数组):条件值 - - 根据操作类型不同,值的形式也不同 - - `in` 操作:值为逗号分隔的字符串,如 `"1,2,3"` - - `between` 操作:值为逗号分隔的两个值,如 `"2024-01-01,2024-12-31"` - - `isnull` 和 `isnotnull` 操作:value 可以省略 - -**示例**: - -```json -{ - "logic": "and", - "items": [ - { - "key": "is_schoolmate", - "operator": "eq", - "value": "1" - }, - { - "key": "company.is_yh_invested", - "operator": "eq", - "value": "1" - }, - { - "key": "created_at", - "operator": "between", - "value": "2024-01-01,2024-12-31" - } - ] -} -``` - ---- - -## 三、统计方式(statistics) - -### 3.1 统计类型(type) - -**说明**:指定统计的方式。 - -**可选值**: -- `sum` - 求和(需要指定 `field` 字段) -- `max` - 最大值(需要指定 `field` 字段) -- `min` - 最小值(需要指定 `field` 字段) -- `count` - 统计总数量(不需要指定 `field` 字段) -- `count_distinct` - 统计去重数量(需要指定 `distinct_field` 字段) - -**示例**: -```json -{ - "type": "count" -} -``` - -### 3.2 统计字段(field) - -**说明**:当统计类型为 `sum`、`max` 或 `min` 时,指定要统计的字段名。 - -**注意**: -- `type` 为 `sum`、`max`、`min` 时必须指定 `field` -- `type` 为 `count` 时可以省略 `field` -- 可以是主模型的字段 -- 可以是关联模型的字段(使用点号分隔,如 `company.company_fund`) - -**示例**: -```json -{ - "type": "sum", - "field": "company_fund" -} -``` - -```json -{ - "type": "max", - "field": "company.company_fund" -} -``` - -```json -{ - "type": "min", - "field": "created_at" -} -``` - -### 3.3 去重字段(distinct_field) - -**说明**:当统计类型为 `count_distinct` 时,指定要去重的字段名。 - -**注意**: -- `type` 为 `count_distinct` 时必须指定 `distinct_field` -- 可以是主模型的字段 -- 可以是关联模型的字段(使用点号分隔,如 `user.mobile`) -- **可以与 `group_by` 同时使用**:可以按某个字段分组,然后统计每个分组的去重数量 - -**示例1:不分组去重统计** -```json -{ - "type": "count_distinct", - "distinct_field": "mobile" -} -``` - -**示例2:关联模型字段去重** -```json -{ - "type": "count_distinct", - "distinct_field": "user.mobile" -} -``` - -**示例3:分组 + 去重统计(组合使用)** -```json -{ - "type": "count_distinct", - "distinct_field": "user.mobile", - "group_by": "course.type" -} -``` - -### 3.4 分组字段(group_by) - -**说明**:指定按哪个字段进行分组统计。这是一个可选配置,可以选择不分组或选择具体的分组字段。 - -**配置选项**: -- **不分组**:不设置 `group_by` 字段,或设置为 `null`,将返回所有符合条件的记录列表 -- **按字段分组**:设置具体的分组字段,将按该字段进行分组统计 - -**分组字段格式**: -- 可以是主模型的字段(如:`company_area`) -- 可以是关联模型的字段(使用点号分隔,如 `company.company_area`) - -**示例1:不分组统计** -```json -{ - "statistics": { - "type": "count" - // 不设置 group_by,表示不分组 - } -} -``` - -**示例2:按主模型字段分组** -```json -{ - "statistics": { - "type": "count", - "group_by": "company_area" - } -} -``` - -**示例3:按关联模型字段分组** -```json -{ - "statistics": { - "type": "count", - "group_by": "company.company_area" - } -} -``` - -**示例4:分组 + 去重统计(组合使用)** -```json -{ - "statistics": { - "type": "count_distinct", - "distinct_field": "user.mobile", - "group_by": "course.type" - } -} -``` - -### 3.4 排序方式(order_by) - -**说明**:指定结果的排序方式。 - -**结构**: -```json -{ - "field": "排序字段", - "direction": "asc|desc" -} -``` - -**字段说明**: -- **field**(字符串):排序字段名 - - 可以是主模型的字段 - - 可以是关联模型的字段(使用点号分隔) - - 可以是统计结果字段(如 `total`、`count`) - -- **direction**(字符串):排序方向 - - `asc` - 升序 - - `desc` - 降序 - -**示例**: -```json -{ - "order_by": { - "field": "total", - "direction": "desc" - } -} -``` - ---- - -## 完整示例 - -### 示例1:统计各区域的校友人数 - -```json -{ - "data_source": { - "main_model": "user", - "relations": ["company"] - }, - "conditions": { - "logic": "and", - "items": [ - { - "key": "is_schoolmate", - "operator": "eq", - "value": "1" - }, - { - "key": "created_at", - "operator": "between", - "value": "2024-01-01,2024-12-31" - } - ] - }, - "statistics": { - "type": "count", - "group_by": "company.company_area", - "order_by": { - "field": "count", - "direction": "desc" - } - } -} -``` - -### 示例2:统计各课程类型的报名人数 - -```json -{ - "data_source": { - "main_model": "course_sign", - "relations": ["course", "user"] - }, - "conditions": { - "logic": "and", - "items": [ - { - "key": "status", - "operator": "eq", - "value": "1" - }, - { - "key": "created_at", - "operator": "between", - "value": "2024-01-01,2024-12-31" - } - ] - }, - "statistics": { - "type": "count", - "group_by": "course.type", - "order_by": { - "field": "count", - "direction": "desc" - } - } -} -``` - -### 示例3:统计各公司的融资总额 - -```json -{ - "data_source": { - "main_model": "company", - "relations": ["user"] - }, - "conditions": { - "logic": "and", - "items": [ - { - "key": "is_yh_invested", - "operator": "eq", - "value": "1" - }, - { - "key": "company_fund", - "operator": "isnotnull" - } - ] - }, - "statistics": { - "type": "sum", - "field": "company_fund", - "group_by": "company_area", - "order_by": { - "field": "total", - "direction": "desc" - } - } -} -``` - -### 示例4:统计审核通过或待审核的报名人数 - -```json -{ - "data_source": { - "main_model": "course_sign", - "relations": [] - }, - "conditions": { - "logic": "or", - "items": [ - { - "key": "status", - "operator": "eq", - "value": "0" - }, - { - "key": "status", - "operator": "eq", - "value": "1" - } - ] - }, - "statistics": { - "type": "count", - "order_by": { - "field": "created_at", - "direction": "desc" - } - } -} -``` - -### 示例5:统计各课程类型的去重培养人数(按手机号去重) - -```json -{ - "data_source": { - "main_model": "course_sign", - "relations": ["user", "course"] - }, - "conditions": { - "logic": "and", - "items": [ - { - "key": "status", - "operator": "eq", - "value": "1" - }, - { - "key": "created_at", - "operator": "between", - "value": "2020-01-01," . date('Y-m-d') - } - ] - }, - "statistics": { - "type": "count_distinct", - "distinct_field": "user.mobile", - "group_by": "course.type", - "order_by": { - "field": "total", - "direction": "desc" - } - } -} -``` - ---- - -## 注意事项 - -1. **字段引用**: - - 主模型字段直接使用字段名 - - 关联模型字段使用 `模型名.字段名` 格式 - - 例如:`company.name`、`course.type` - -2. **数据类型**: - - 所有值在 JSON 中都存储为字符串 - - 系统会根据字段类型自动转换 - -3. **条件逻辑**: - - `and` 表示所有条件都必须满足 - - `or` 表示至少一个条件满足 - - 条件数组可以为空(表示无条件) - -4. **统计字段**: - - `sum` 类型必须指定 `field` - - `count` 类型不需要 `field` - - 分组字段可以为空(表示不分组) - -5. **排序字段**: - - 可以按任意字段排序 - - 可以按统计结果字段排序(如 `total`、`count`) - - 排序字段可以为空(使用默认排序) - ---- - -## 文档版本 - -- **创建日期**:2025-11-19 -- **最后更新**:2025-11-19 -