diff --git a/app/Console/Commands/UpdateUserNo.php b/app/Console/Commands/UpdateUserNo.php index 408f2cf..4e56e31 100755 --- a/app/Console/Commands/UpdateUserNo.php +++ b/app/Console/Commands/UpdateUserNo.php @@ -23,7 +23,7 @@ class UpdateUserNo extends Command * * @var string */ - protected $description = '批量更新学号'; + protected $description = '批量更新学号/打元和同事标签'; /** * Create a new command instance. @@ -42,6 +42,21 @@ class UpdateUserNo extends Command */ public function handle() { + // 1. 批量更新学号 + $this->updateUserNo(); + + // 2. 给元和同事打标签 + $this->tagYuanheColleague(); + + return $this->info('更新完成'); + } + + /** + * 批量更新学号 + */ + protected function updateUserNo() + { + $this->info('开始更新学号...'); // 已经开始的课程日期(所有历史数据处理) // $dateList = Course::whereNotNull('start_date') // ->where('start_date', '<=', date('Y-m-d')) @@ -52,9 +67,11 @@ class UpdateUserNo extends Command // 当日数据处理(日常定时任务) $dateList = [date('Y-m-d')]; foreach ($dateList as $date) { - $courses = Course::with(['courseSigns' => function ($query) { - $query->where('status', 1); - }])->where('start_date', $date) + $courses = Course::with([ + 'courseSigns' => function ($query) { + $query->where('status', 1); + } + ])->where('start_date', $date) ->whereNotNull('student_prefix') ->orderBy('start_date') ->get(); @@ -70,12 +87,50 @@ class UpdateUserNo extends Command // 更新用户编号 $user->no = $no; $user->save(); - $this->info($no); + $this->info('学号: ' . $no); $i++; } } } - return $this->info('更新完成'); + $this->info('学号更新完成'); + } + + /** + * 给元和同事打标签 + */ + protected function tagYuanheColleague() + { + $this->info('开始给元和同事打标签...'); + $tag = '元禾同事'; + + // 获取元和员工用户列表 + $users = CourseSign::companyJoin(null, null, null, true); + + $count = 0; + foreach ($users as $user) { + // 获取当前的 from 字段 + $from = $user->from ?? ''; + + // 将 from 字段按逗号分隔成数组 + $fromArray = array_filter(array_map('trim', explode(',', $from))); + + // 检查是否已存在该标签 + if (in_array($tag, $fromArray)) { + continue; + } + + // 追加标签 + $fromArray[] = $tag; + + // 更新 from 字段 + $user->from = implode(',', $fromArray); + $user->save(); + + $this->info('已为用户 ' . $user->name . '(' . $user->mobile . ') 添加标签: ' . $tag); + $count++; + } + + $this->info('元和同事标签更新完成,共更新 ' . $count . ' 人'); } } diff --git a/app/Exports/CommonExport.php b/app/Exports/CommonExport.php index 97daf33..4367da7 100755 --- a/app/Exports/CommonExport.php +++ b/app/Exports/CommonExport.php @@ -8,11 +8,34 @@ namespace App\Exports; use App\Exceptions\ErrorException; use Illuminate\Support\Collection; use Maatwebsite\Excel\Concerns\FromCollection; +use Maatwebsite\Excel\Concerns\WithStyles; +use Maatwebsite\Excel\Concerns\WithColumnWidths; +use Maatwebsite\Excel\Concerns\WithEvents; +use Maatwebsite\Excel\Events\AfterSheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Style\Alignment; -class CommonExport implements FromCollection +class CommonExport implements FromCollection, WithStyles, WithColumnWidths, WithEvents { public $fields; public $data; + public $hasUsersField = false; + public $usersColumnStartIndex = null; + public $totalColumns = 0; + public $totalRows = 0; + public $expandedFields = []; + + // 学员信息子列定义 + const USERS_SUB_COLUMNS = [ + 'user_index' => '序号', + 'user_no' => '学号', + 'user_name' => '姓名', + 'user_schoolmate' => '校友', + 'user_position' => '职位', + 'user_mobile' => '手机', + 'user_course' => '报名课程', + 'user_sign_date' => '报名时间', + ]; public function __construct($data, $exportFields) { @@ -20,8 +43,206 @@ class CommonExport implements FromCollection $this->fields = $exportFields; // 数据 $this->data = $data; + + // 构建扩展后的字段列表 + $this->buildExpandedFields(); + } + + /** + * 构建扩展后的字段列表(将users字段展开成多列) + */ + private function buildExpandedFields() + { + if (!is_array($this->fields)) { + return; + } + + $index = 1; + foreach ($this->fields as $field => $label) { + if (str_contains($field, 'users')) { + $this->hasUsersField = true; + $this->usersColumnStartIndex = $index; + // 展开学员信息为多列 + foreach (self::USERS_SUB_COLUMNS as $subField => $subLabel) { + $this->expandedFields[$subField] = $subLabel; + $index++; + } + } else { + $this->expandedFields[$field] = $label; + $index++; + } + } + $this->totalColumns = count($this->expandedFields); + } + + /** + * 获取列字母 + */ + private function getColumnLetter($columnNumber) + { + $letter = ''; + while ($columnNumber > 0) { + $columnNumber--; + $letter = chr(65 + ($columnNumber % 26)) . $letter; + $columnNumber = intval($columnNumber / 26); + } + return $letter; + } + + /** + * 设置列宽 + */ + public function columnWidths(): array + { + $widths = []; + $index = 1; + foreach (array_keys($this->expandedFields) as $field) { + $letter = $this->getColumnLetter($index); + if (in_array($field, ['user_course', 'user_name'])) { + $widths[$letter] = 25; + } elseif (in_array($field, ['user_mobile', 'user_sign_date'])) { + $widths[$letter] = 15; + } elseif (in_array($field, ['user_index', 'user_schoolmate'])) { + $widths[$letter] = 8; + } elseif (str_contains($field, 'partners') || str_contains($field, 'project_users')) { + $widths[$letter] = 50; + } elseif (str_contains($field, 'all_course')) { + $widths[$letter] = 40; + } elseif (str_contains($field, 'company_name') || str_contains($field, 'address')) { + $widths[$letter] = 30; + } else { + $widths[$letter] = 15; + } + $index++; + } + return $widths; } + /** + * 设置样式 + */ + public function styles(Worksheet $sheet): array + { + $lastCol = $this->getColumnLetter($this->totalColumns); + $dataStartRow = $this->hasUsersField ? 3 : 2; + + $styles = []; + + if ($this->hasUsersField) { + // 二级表头样式 - 第一行 + $styles[1] = [ + 'font' => ['bold' => true, 'size' => 12], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'fill' => [ + 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, + 'startColor' => ['rgb' => 'D0D0D0'], + ], + ]; + // 二级表头样式 - 第二行 + $styles[2] = [ + 'font' => ['bold' => true, 'size' => 11], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'fill' => [ + 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, + 'startColor' => ['rgb' => 'E8E8E8'], + ], + ]; + } else { + // 单行表头样式 + $styles[1] = [ + 'font' => ['bold' => true, 'size' => 12], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'fill' => [ + 'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID, + 'startColor' => ['rgb' => 'E0E0E0'], + ], + ]; + } + + // 所有数据区域样式 + $styles["A1:{$lastCol}" . ($this->totalRows + $dataStartRow - 1)] = [ + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_CENTER, + 'wrapText' => true, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN, + 'color' => ['rgb' => 'CCCCCC'], + ], + ], + ]; + + return $styles; + } + + /** + * 注册事件 + */ + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + $sheet = $event->sheet->getDelegate(); + + if ($this->hasUsersField) { + // 处理二级表头的单元格合并 + $this->mergeHeaderCells($sheet); + + // 设置表头行高 + $sheet->getRowDimension(1)->setRowHeight(25); + $sheet->getRowDimension(2)->setRowHeight(22); + + // 冻结前两行 + $sheet->freezePane('A3'); + } else { + $sheet->getRowDimension(1)->setRowHeight(25); + $sheet->freezePane('A2'); + } + + // 设置数据行高 + $dataStartRow = $this->hasUsersField ? 3 : 2; + for ($row = $dataStartRow; $row <= $this->totalRows + $dataStartRow - 1; $row++) { + $sheet->getRowDimension($row)->setRowHeight(22); + } + }, + ]; + } + + /** + * 合并表头单元格 + */ + private function mergeHeaderCells(Worksheet $sheet) + { + $index = 1; + foreach ($this->fields as $field => $label) { + if (str_contains($field, 'users')) { + // 学员信息列:合并第一行的多个单元格 + $startCol = $this->getColumnLetter($index); + $endCol = $this->getColumnLetter($index + count(self::USERS_SUB_COLUMNS) - 1); + $sheet->mergeCells("{$startCol}1:{$endCol}1"); + $sheet->setCellValue("{$startCol}1", $label); + + // 学员信息子表头不需要合并 + $index += count(self::USERS_SUB_COLUMNS); + } else { + // 非学员信息列:合并第一行和第二行 + $col = $this->getColumnLetter($index); + $sheet->mergeCells("{$col}1:{$col}2"); + $sheet->setCellValue("{$col}1", $label); + $index++; + } + } + } /** * 数组转集合 @@ -36,65 +257,172 @@ class CommonExport implements FromCollection if (!is_array($this->fields)) { throw new ErrorException('导出字段必须是数组'); } - // 获取表头 - $header = array_values($this->fields); - $moreFileds = []; - if (empty($clear)) { - // 表头追加附属数据 - if (isset($this->data[0]['data']) && is_array($this->data[0]['data'])) { - $moreHeader = array_column($this->data[0]['data'], 'name'); - // 获取头信息 - $header = array_merge($header, $moreHeader); - // 获取字段信息 - $moreFileds = array_column($this->data[0]['data'], 'field'); - } - } - // 获取字段指向 - $fields = array_keys($this->fields); + $newList = []; - foreach ($this->data as $info) { - $temp = []; - foreach ($fields as $field) { - if (str_contains($field, 'idcard')) { - // 身份证 - $temp[$field] = ' ' . $this->getDotValue($info, $field); - } elseif (str_contains($field, 'all_course')) { - // 所有课程 - $temp[$field] = $this->allCourse($info); - } elseif (str_contains($field, 'partners')) { - // 股东信息 - $temp[$field] = $this->partners($info); - } elseif (str_contains($field, 'project_users')) { - // 项目经理 - $temp[$field] = $this->projectManager($info); - } elseif (str_contains($field, 'users')) { - // 学员信息 - $temp[$field] = $this->getUsers($info); + + if ($this->hasUsersField) { + // 有学员字段:创建二级表头 + // 第一行表头(主表头)- 在 mergeHeaderCells 中处理 + $header1 = []; + foreach ($this->fields as $field => $label) { + if (str_contains($field, 'users')) { + // 学员信息占用多列,第一行只需要占位 + foreach (self::USERS_SUB_COLUMNS as $subLabel) { + $header1[] = ''; + } + } else { + $header1[] = ''; + } + } + $newList[] = $header1; + + // 第二行表头(子表头) + $header2 = []; + foreach ($this->fields as $field => $label) { + if (str_contains($field, 'users')) { + foreach (self::USERS_SUB_COLUMNS as $subLabel) { + $header2[] = $subLabel; + } } else { - $temp[$field] = $this->getDotValue($info, $field); + $header2[] = ''; } } - // 如果有自定义数据,全部附件上去 - $t2 = []; + $newList[] = $header2; + + // 数据行:每个学员的每条课程记录占一行 + foreach ($this->data as $info) { + $usersData = $this->getUsersExpanded($info); + if (empty($usersData)) { + // 没有学员数据,输出一行空数据 + $row = $this->buildRowWithoutUsers($info, []); + $newList[] = $row; + } else { + // 每个学员记录一行 + foreach ($usersData as $userData) { + $row = $this->buildRowWithoutUsers($info, $userData); + $newList[] = $row; + } + } + } + } else { + // 没有学员字段:使用原有逻辑 + $header = array_values($this->fields); + $moreFileds = []; if (empty($clear)) { - if (isset($info['data']) && $info['data'] && !empty($moreFileds)) { - $dataCollect = collect($info['data']); - foreach ($moreFileds as $moreFiled) { - $value = ($dataCollect->where('field', $moreFiled)->first()['value']) ?? ''; - if (str_contains($moreFiled, 'idcard')) { - $t2[$moreFiled] = ' ' . $value; - } else { - $t2[$moreFiled] = $value; + if (isset($this->data[0]['data']) && is_array($this->data[0]['data'])) { + $moreHeader = array_column($this->data[0]['data'], 'name'); + $header = array_merge($header, $moreHeader); + $moreFileds = array_column($this->data[0]['data'], 'field'); + } + } + $newList[] = $header; + + foreach ($this->data as $info) { + $temp = []; + foreach (array_keys($this->fields) as $field) { + if (str_contains($field, 'idcard')) { + $temp[$field] = ' ' . $this->getDotValue($info, $field); + } elseif (str_contains($field, 'all_course')) { + $temp[$field] = $this->allCourse($info); + } elseif (str_contains($field, 'partners')) { + $temp[$field] = $this->partners($info); + } elseif (str_contains($field, 'project_users')) { + $temp[$field] = $this->projectManager($info); + } else { + $temp[$field] = $this->getDotValue($info, $field); + } + } + $t2 = []; + if (empty($clear)) { + if (isset($info['data']) && $info['data'] && !empty($moreFileds)) { + $dataCollect = collect($info['data']); + foreach ($moreFileds as $moreFiled) { + $value = ($dataCollect->where('field', $moreFiled)->first()['value']) ?? ''; + if (str_contains($moreFiled, 'idcard')) { + $t2[$moreFiled] = ' ' . $value; + } else { + $t2[$moreFiled] = $value; + } } } } + $newList[] = array_values($temp + $t2); } - $newList[] = $temp + $t2; } - array_unshift($newList, $header); //插入表头 + + $this->totalRows = count($newList) - ($this->hasUsersField ? 2 : 1); return new Collection($newList); } + /** + * 获取展开的学员数据(每个学员每条课程一行) + */ + private function getUsersExpanded($info) + { + if (empty($info['users'])) { + return []; + } + + $result = []; + $userIndex = 1; + foreach ($info['users'] as $user) { + if (!empty($user['course_signs'])) { + foreach ($user['course_signs'] as $signIndex => $sign) { + $result[] = [ + 'user_index' => $signIndex === 0 ? $userIndex : '', + 'user_no' => $signIndex === 0 ? ($user['no'] ?? '-') : '', + 'user_name' => $signIndex === 0 ? ($user['username'] ?? '-') : '', + 'user_schoolmate' => $signIndex === 0 ? ($user['is_schoolmate_text'] ?? '-') : '', + 'user_position' => $signIndex === 0 ? ($user['company_position'] ?? '-') : '', + 'user_mobile' => $signIndex === 0 ? ($user['mobile'] ?? '-') : '', + 'user_course' => $sign['course']['name'] ?? '-', + 'user_sign_date' => isset($sign['created_at']) ? substr($sign['created_at'], 0, 10) : '-', + ]; + } + } else { + $result[] = [ + 'user_index' => $userIndex, + 'user_no' => $user['no'] ?? '-', + 'user_name' => $user['username'] ?? '-', + 'user_schoolmate' => $user['is_schoolmate_text'] ?? '-', + 'user_position' => $user['company_position'] ?? '-', + 'user_mobile' => $user['mobile'] ?? '-', + 'user_course' => '-', + 'user_sign_date' => '-', + ]; + } + $userIndex++; + } + return $result; + } + + /** + * 构建包含学员数据的行 + */ + private function buildRowWithoutUsers($info, $userData) + { + $row = []; + foreach ($this->fields as $field => $label) { + if (str_contains($field, 'users')) { + // 填充学员信息列 + foreach (array_keys(self::USERS_SUB_COLUMNS) as $subField) { + $row[] = $userData[$subField] ?? ''; + } + } elseif (str_contains($field, 'idcard')) { + $row[] = ' ' . $this->getDotValue($info, $field); + } elseif (str_contains($field, 'all_course')) { + $row[] = $this->allCourse($info); + } elseif (str_contains($field, 'partners')) { + $row[] = $this->partners($info); + } elseif (str_contains($field, 'project_users')) { + $row[] = $this->projectManager($info); + } else { + $row[] = $this->getDotValue($info, $field); + } + } + return $row; + } + /** * .号转数组层级并返回对应的值 * @param $key @@ -161,21 +489,4 @@ class CommonExport implements FromCollection return implode("、\r\n", $list); } - /** - * 获取手机号 - * @param $data - */ - function getUsers($data) - { - $list = []; - foreach ($data['users'] as $item) { - $base = $item['no'] . '-' . $item['username'] . '-' . $item['is_schoolmate_text'] . '-' . $item['company_position'] . '-' . ($item['mobile'] ?? ''); - foreach ($item['course_signs'] as $i) { - $base .= '-' . $i['course']['name'] . '-' . ($i['created_at'] ?? ''); - } - $list[] = $base; - } - return implode("、\r\n", $list); - } - } diff --git a/app/Http/Controllers/Admin/CalendarsController.php b/app/Http/Controllers/Admin/CalendarsController.php index e3d7d62..1490dbf 100644 --- a/app/Http/Controllers/Admin/CalendarsController.php +++ b/app/Http/Controllers/Admin/CalendarsController.php @@ -12,6 +12,7 @@ use App\Models\CourseContentEvaluationAsk; use App\Models\CourseContentEvaluationForm; use App\Models\CustomForm; use App\Models\CustomFormField; +use App\Models\HistoryCourse; use App\Models\SupplyDemand; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; @@ -49,7 +50,7 @@ class CalendarsController extends BaseController public function index() { $all = \request()->all(); - $list = Calendar::with('course', 'courseContent') + $list = Calendar::with('course', 'courseContent','historyCourses') ->where(function ($query) use ($all) { if (isset($all['month'])) { $query->where('start_time', 'like', $all['month'] . '%'); @@ -90,7 +91,7 @@ class CalendarsController extends BaseController if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } - $detail = $this->model->with('courseContent')->find($all['id']); + $detail = $this->model->with(['courseContent', 'historyCourses'])->find($all['id']); return $this->success($detail); } @@ -113,6 +114,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="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"), * @OA\Response( * response="200", @@ -122,7 +124,39 @@ class CalendarsController extends BaseController */ public function save() { - return parent::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; + $all['admin_id'] = $this->getUserId(); + $all['department_id'] = $this->getUser()->department_id; + } + $original = $model->getOriginal(); + $model->fill($all); + $model->save(); + + // 处理历史课程数据 + if (isset($all['history_courses']) && is_array($all['history_courses'])) { + // 删除原有的历史课程数据 + $model->historyCourses()->delete(); + $model->historyCourses()->createMany($all['history_courses']); + } + DB::commit(); + // 记录日志 + $this->saveLogs($original, $model); + // 返回带有历史课程的数据 + $model->load('historyCourses'); + return $this->success($model); + } catch (\Exception $exception) { + DB::rollBack(); + return $this->fail([$exception->getCode(), $exception->getMessage()]); + } } /** @@ -141,7 +175,33 @@ class CalendarsController extends BaseController */ public function destroy() { - return parent::destroy(); + $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())]); + } + + DB::beginTransaction(); + try { + $model = $this->model->find($all['id']); + if (empty($model)) { + return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']); + } + // 删除关联的历史课程数据 + $model->historyCourses()->delete(); + // 删除日历 + $model->delete(); + DB::commit(); + return $this->success([]); + } catch (\Exception $exception) { + DB::rollBack(); + return $this->fail([$exception->getCode(), $exception->getMessage()]); + } } } diff --git a/app/Http/Controllers/Admin/CourseSignController.php b/app/Http/Controllers/Admin/CourseSignController.php index 66f8c75..60e5a09 100755 --- a/app/Http/Controllers/Admin/CourseSignController.php +++ b/app/Http/Controllers/Admin/CourseSignController.php @@ -86,7 +86,9 @@ class CourseSignController extends BaseController $query->where('is_vip', $all['is_vip']); } if (isset($all['company_name'])) { - $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); + $query->whereHas('company', function ($query) use ($all) { + $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); + }); } if (isset($all['company_position'])) { $query->where('company_position', $all['company_position']); diff --git a/app/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index 7ad7bbb..b14c587 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -128,7 +128,7 @@ class OtherController extends CommonController $list['schoolmate_year'] = User::where('is_schoolmate', 1)->where('created_at', 'like', '%' . date('Y') . '%')->count(); // 投后企业 $list['company_invested_total'] = CourseSign::yhInvested(); - // 元和员工参与企业 + // 元和员工参与人数 $list['company_join_total'] = CourseSign::companyJoin(); // 全市干部参与企业 $list['company_ganbu_total'] = CourseSign::ganbu(); @@ -236,7 +236,7 @@ class OtherController extends CommonController })->whereIn('course_id', $courses->pluck('id')); $list['course_total'] = (clone $calendar)->count(); // 开课天数 - $list['course_day_total'] = (clone $calendar)->sum('days'); + $list['course_day_total'] = (clone $calendar)->where('is_count_days', 1)->sum('days'); // 上市公司数(所有上市公司) $list['company_market_total'] = Company::companyMarket($start_date, $end_date); @@ -251,7 +251,10 @@ class OtherController extends CommonController // 入学后上市公司数量(在指定时间范围内报名的学员所在公司中,在入学后上市的公司数量) $list['company_market_after_enrollment_total'] = CourseSign::companyMarketAfterEnrollment($start_date, $end_date, $course_ids); - // 元和员工参与企业 + // 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) + $list['company_invested_after_enrollment_total'] = CourseSign::companyInvestedAfterEnrollment($start_date, $end_date, $course_ids); + + // 元和员工参与人数 $list['company_join_total'] = CourseSign::companyJoin($start_date, $end_date, $course_ids); // 全市干部参与企业 $list['company_ganbu_total'] = CourseSign::ganbu($start_date, $end_date, $course_ids); @@ -268,7 +271,14 @@ class OtherController extends CommonController $courseTypes = CourseType::whereIn('id', $course_type_id)->get(); foreach ($courseTypes as $courseType) { // 获取课程 - $courses2 = Course::where('type', $courseType->id)->orderBy('start_date', 'asc')->get(); + $courses2 = Course::where('type', $courseType->id) + ->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]); + } + })->orderBy('start_date', 'asc')->get(); foreach ($courses2 as $course) { $courseTypesSum[] = [ 'course_type' => $courseType->name, @@ -282,7 +292,9 @@ class OtherController extends CommonController } } // 附加历史课程数据 - $historyCourses = HistoryCourse::where(function ($query) use ($start_date, $end_date) { + $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]); @@ -310,7 +322,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-入学后上市公司明细, 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-入学后被投企业明细, 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,多个英文逗号"), @@ -632,6 +644,39 @@ class OtherController extends CommonController $filename = '入学后上市公司明细'; break; + case 'company_invested_after_enrollment_total': + // 入学后被投企业明细 - 使用模型方法 + $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_sign_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' => '法人', + 'company_date' => '成立时间', + 'invest_date' => '被投日期', + 'first_sign_date' => '首次报名时间', + 'user_names' => '学员姓名', + 'company_address' => '地址', + 'company_city' => '所在城市', + 'company_area' => '所在区域', + ]; + $filename = '入学后被投企业明细'; + break; + case 'course_total': // 开课场次明细 - 与coursesHome算法一致 $calendars = Calendar::whereIn('course_id', $course_ids) @@ -691,35 +736,33 @@ class OtherController extends CommonController break; case 'company_join_total': - // 元和员工参与企业明细 - 使用模型方法 - $companies = CourseSign::companyJoin($start_date, $end_date, $course_ids, true); - foreach ($companies as $company) { + // 元和员工参与人员明细 - 使用模型方法(现在返回的是用户列表) + $users = CourseSign::companyJoin($start_date, $end_date, $course_ids, true); + // 加载关联关系 + $users->load('company'); + foreach ($users as $user) { $data[] = [ - '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 ?? '', - '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 ?? '', + 'user_name' => $user->name ?? '', + 'mobile' => $user->mobile ?? '', + 'company_name' => $user->company->company_name ?? '', + 'company_position' => $user->company_position ?? '', + 'company_city' => $user->company->company_city ?? '', + 'company_area' => $user->company->company_area ?? '', + 'company_legal_representative' => $user->company->company_legal_representative ?? '', + 'company_date' => $user->company->company_date ?? '', + 'company_address' => $user->company->company_address ?? '', ]; } $fields = [ + 'user_name' => '学员姓名', + 'mobile' => '手机号', 'company_name' => '企业名称', + 'company_position' => '职位', + 'company_city' => '所在城市', + 'company_area' => '所在区域', 'company_legal_representative' => '法人', 'company_date' => '成立时间', 'company_address' => '地址', - 'company_city' => '所在城市', - 'company_area' => '所在区域', - 'business_scope' => '营业范围', - 'contact_phone' => '联系电话', - 'contact_mail' => '联系邮箱', - 'company_tag' => '企业资质', - 'credit_code' => '统一社会信用代码', ]; $filename = '元和员工参与企业明细'; break; diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 9c1bf21..6d1438a 100755 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -291,7 +291,9 @@ class UserController extends BaseController $query->where('name', 'like', '%' . $all['name'] . '%'); } if (isset($all['company_name'])) { - $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); + $query->whereHas('company', function ($query) use ($all) { + $query->where('company_name', 'like', '%' . $all['company_name'] . '%'); + }); } if (isset($all['company_position'])) { $query->where('company_position', $all['company_position']); diff --git a/app/Models/Calendar.php b/app/Models/Calendar.php index 172b6ba..9cde227 100755 --- a/app/Models/Calendar.php +++ b/app/Models/Calendar.php @@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Cache; class Calendar extends SoftDeletesModel { - protected $appends = ['is_publish_text','type_text']; + protected $appends = ['is_publish_text', 'type_text', 'is_count_days_text', 'is_count_people_text']; public function getIsPublishTextAttribute() { @@ -22,6 +22,16 @@ class Calendar extends SoftDeletesModel return $array[$this->attributes['type']] ?? ''; } + public function getIsCountDaysTextAttribute() + { + return ($this->attributes['is_count_days'] ?? 1) == 1 ? '是' : '否'; + } + + public function getIsCountPeopleTextAttribute() + { + return ($this->attributes['is_count_people'] ?? 1) == 1 ? '是' : '否'; + } + public function course() { return $this->hasOne(Course::class, 'id', 'course_id'); @@ -32,5 +42,10 @@ class Calendar extends SoftDeletesModel return $this->hasMany(CourseContent::class, 'id', 'course_content_id'); } + public function historyCourses() + { + return $this->hasMany(HistoryCourse::class, 'calendar_id', 'id'); + } + } diff --git a/app/Models/CourseSign.php b/app/Models/CourseSign.php index 317078b..e7178c7 100755 --- a/app/Models/CourseSign.php +++ b/app/Models/CourseSign.php @@ -110,7 +110,9 @@ class CourseSign extends SoftDeletesModel // 基础数据 $baseTotal = $totalQuery->count(); // 历史数据 - $historyTotal = HistoryCourse::where(function ($query) use ($start_date, $end_date) { + $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]); @@ -133,7 +135,9 @@ class CourseSign extends SoftDeletesModel } else { $baseTotal = $user->count(); // 历史数据 - $historyTotal = HistoryCourse::where(function ($query) use ($start_date, $end_date) { + $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]); @@ -173,7 +177,7 @@ class CourseSign extends SoftDeletesModel { $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); $courseSigns = $courseSignsQuery->whereHas('user', function ($query) { - $query->where('from', '跟班学员'); + $query->where('from', 'like', '%跟班学员%'); })->get(); if ($retList) { @@ -223,6 +227,59 @@ class CourseSign extends SoftDeletesModel } } + /** + * 入学后被投企业数量(在指定时间范围内报名的学员所在公司中,在入学后被投的公司数量) + * @param string $start_date 开始日期 + * @param string $end_date 结束日期 + * @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')->get(); + $companiesAfterEnrollment = []; + foreach ($courseSignsForInvest as $sign) { + if ($sign->user && $sign->user->company && $sign->user->company->is_yh_invested == 1) { + $signDate = \Carbon\Carbon::parse($sign->created_at)->format('Y-m-d'); + // 从 project_users 中获取最早的被投时间 + $projectUsers = $sign->user->company->project_users; + $investDate = null; + if (!empty($projectUsers) && is_array($projectUsers)) { + foreach ($projectUsers as $projectUser) { + if (!empty($projectUser['investDate'])) { + if ($investDate === null || $projectUser['investDate'] < $investDate) { + $investDate = $projectUser['investDate']; + } + } + } + } + // 被投时间 >= 报名时间,说明是入学后被投 + if ($investDate && $investDate >= $signDate) { + $companyId = $sign->user->company->id; + if (!isset($companiesAfterEnrollment[$companyId])) { + $companiesAfterEnrollment[$companyId] = [ + 'company' => $sign->user->company, + 'first_sign_date' => $signDate, + 'invest_date' => $investDate, + 'users' => [], + ]; + } + if ($retList) { + $companiesAfterEnrollment[$companyId]['users'][] = $sign->user; + } + } + } + } + + if ($retList) { + return $companiesAfterEnrollment; + } else { + return count($companiesAfterEnrollment); + } + } + /** * 区域统计 * @param string $start_date 开始日期 @@ -275,11 +332,11 @@ class CourseSign extends SoftDeletesModel } /** - * 元和员工参与企业 + * 元和员工参人员 */ public static function companyJoin($start_date = null, $end_date = null, $course_ids = null, $retList = false) { - $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); + $courseSignsQuery = self::getStudentList($start_date, $end_date, null, $course_ids); $courseSignByType = $courseSignsQuery->get(); // 检测关键词 @@ -289,13 +346,15 @@ class CourseSign extends SoftDeletesModel '禾裕集团', '苏州科服', '信诚管理咨询', '集成电路公司', '常州团队', '国企元禾' ]; - $list = Company::whereHas('users', function ($query) use ($courseSignByType, $companyNameKeyword) { - $query->whereIn('id', $courseSignByType->pluck('user_id')); - })->where(function ($query) use ($companyNameKeyword) { + $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')) + ->whereIn('company_id', $company->pluck('id')) + ->get(); + if ($retList) { // 返回列表 return $list; @@ -317,7 +376,7 @@ class CourseSign extends SoftDeletesModel { $courseSignsQuery = self::getStudentList($start_date, $end_date, 1, $course_ids); $courseSigns = $courseSignsQuery->whereHas('user', function ($query) { - $query->where('from', '跟班学员'); + $query->where('from', 'like', '%跟班学员%'); })->get(); if ($retList) { return User::with('company')->whereIn('id', $courseSigns->pluck('user_id'))->get(); diff --git a/app/Models/HistoryCourse.php b/app/Models/HistoryCourse.php index 4eb14bf..8c5a203 100644 --- a/app/Models/HistoryCourse.php +++ b/app/Models/HistoryCourse.php @@ -9,5 +9,10 @@ class HistoryCourse extends SoftDeletesModel { return $this->hasOne(CourseType::class, 'id', 'type'); } + + public function calendar() + { + return $this->belongsTo(Calendar::class, 'calendar_id', 'id'); + } } diff --git a/database/migrations/2025_11_26_100000_add_calendar_id_to_history_courses_table.php b/database/migrations/2025_11_26_100000_add_calendar_id_to_history_courses_table.php new file mode 100644 index 0000000..20aafa5 --- /dev/null +++ b/database/migrations/2025_11_26_100000_add_calendar_id_to_history_courses_table.php @@ -0,0 +1,34 @@ +unsignedBigInteger('calendar_id')->nullable()->after('id')->comment('日历ID'); + $table->index('calendar_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('history_courses', function (Blueprint $table) { + $table->dropIndex(['calendar_id']); + $table->dropColumn('calendar_id'); + }); + } +}; + diff --git a/database/migrations/2025_11_26_100100_add_count_fields_to_calendars_table.php b/database/migrations/2025_11_26_100100_add_count_fields_to_calendars_table.php new file mode 100644 index 0000000..9a537ab --- /dev/null +++ b/database/migrations/2025_11_26_100100_add_count_fields_to_calendars_table.php @@ -0,0 +1,33 @@ +tinyInteger('is_count_days')->nullable()->comment('是否统计天数 0否 1是'); + $table->tinyInteger('is_count_people')->nullable()->comment('是否统计人数 0否 1是'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('calendars', function (Blueprint $table) { + $table->dropColumn(['is_count_days', 'is_count_people']); + }); + } +}; +