prefix = config('database.connections.mysql.prefix'); } /** * 获取课程安排列表(支持多维度筛选) * @param array $data 筛选参数 * @return array 课程安排列表数据 */ public function getScheduleList($data = []) { try { // 构建查询条件 $where = $this->buildScheduleWhere($data); // 分页参数 $page = intval($data['page'] ?? 1); $limit = intval($data['limit'] ?? 20); $offset = ($page - 1) * $limit; // 基础查询 $query = Db::name('course_schedule') ->alias('cs') ->leftJoin($this->prefix . 'course c', 'cs.course_id = c.id') ->leftJoin($this->prefix . 'venue v', 'cs.venue_id = v.id') ->leftJoin($this->prefix . 'campus cap', 'cs.campus_id = cap.id') ->leftJoin($this->prefix . 'personnel coach', 'cs.coach_id = coach.id') ->leftJoin($this->prefix . 'personnel edu', 'cs.education_id = edu.id') ->leftJoin($this->prefix . 'class cla', 'cs.class_id = cla.id') ->where($where) ->where('cs.deleted_at', 0); // 获取总数 $total = $query->count(); // 获取列表数据 $list = $query->field([ 'cs.*', 'c.course_name', 'c.course_type', 'c.duration as course_duration', 'c.session_count', 'c.single_session_count', 'v.venue_name', 'v.capacity as venue_capacity', 'cap.campus_name', 'coach.name as coach_name', 'coach.head_img as coach_avatar', 'edu.name as education_name', 'cla.class_name', ]) ->order('cs.course_date DESC, cs.time_slot ASC') ->limit($offset, $limit) ->select() ->toArray(); // 处理列表数据 foreach ($list as &$item) { // 解析时间段 $item['time_info'] = $this->parseTimeSlot($item['time_slot']); // 获取参与学员信息 $item['students'] = $this->getScheduleStudents($item['id']); // 获取助教信息 $item['assistants'] = $this->getScheduleAssistants($item['assistant_ids']); // 计算已报名人数 $item['enrolled_count'] = count($item['students']); // 计算剩余容量 $item['remaining_capacity'] = max(0, ($item['available_capacity'] ?? $item['venue_capacity']) - $item['enrolled_count']); // 格式化状态 $item['status_text'] = $this->getStatusText($item['status']); // 格式化创建方式 $item['created_by_text'] = $item['created_by'] == 'manual' ? '手动安排' : '系统创建'; // 处理图片路径 $item['coach_avatar'] = $item['coach_avatar'] ? $this->formatImageUrl($item['coach_avatar']) : ''; } return [ 'list' => $list, 'total' => $total, 'page' => $page, 'limit' => $limit, 'pages' => ceil($total / $limit) ]; } catch (\Exception $e) { return [ 'list' => [], 'total' => 0, 'page' => 1, 'limit' => $limit ?? 20, 'pages' => 0, 'error' => $e->getMessage() ]; } } /** * 构建查询条件 * @param array $data 筛选参数 * @return array 条件数组 */ private function buildScheduleWhere($data) { $where = []; // 日期范围筛选 if (!empty($data['start_date'])) { $where[] = ['cs.course_date', '>=', $data['start_date']]; } if (!empty($data['end_date'])) { $where[] = ['cs.course_date', '<=', $data['end_date']]; } // 校区筛选 if (!empty($data['campus_id'])) { if (is_array($data['campus_id'])) { $where[] = ['cs.campus_id', 'in', $data['campus_id']]; } else { $where[] = ['cs.campus_id', '=', $data['campus_id']]; } } // 场地筛选 if (!empty($data['venue_id'])) { if (is_array($data['venue_id'])) { $where[] = ['cs.venue_id', 'in', $data['venue_id']]; } else { $where[] = ['cs.venue_id', '=', $data['venue_id']]; } } // 教练筛选 if (!empty($data['coach_id'])) { if (is_array($data['coach_id'])) { $where[] = ['cs.coach_id', 'in', $data['coach_id']]; } else { $where[] = ['cs.coach_id', '=', $data['coach_id']]; } } // 课程筛选 if (!empty($data['course_id'])) { if (is_array($data['course_id'])) { $where[] = ['cs.course_id', 'in', $data['course_id']]; } else { $where[] = ['cs.course_id', '=', $data['course_id']]; } } // 状态筛选 if (!empty($data['status'])) { if (is_array($data['status'])) { $where[] = ['cs.status', 'in', $data['status']]; } else { $where[] = ['cs.status', '=', $data['status']]; } } // 教务筛选 if (!empty($data['education_id'])) { $where[] = ['cs.education_id', '=', $data['education_id']]; } // 时间段筛选 if (!empty($data['time_range'])) { switch ($data['time_range']) { case 'morning': $where[] = ['cs.time_slot', 'like', '0%']; break; case 'afternoon': $where[] = ['cs.time_slot', 'like', '1%']; break; case 'evening': $where[] = ['cs.time_slot', 'like', '1[8-9]%']; break; } } // 自动排课筛选 if (isset($data['auto_schedule'])) { $where[] = ['cs.auto_schedule', '=', $data['auto_schedule']]; } // 创建方式筛选 if (!empty($data['created_by'])) { $where[] = ['cs.created_by', '=', $data['created_by']]; } return $where; } /** * 解析时间段 * @param string $timeSlot 时间段字符串(格式如:09:00-10:30) * @return array 解析后的时间段信息 */ private function parseTimeSlot($timeSlot) { if (strpos($timeSlot, '-') !== false) { list($startTime, $endTime) = explode('-', $timeSlot); return [ 'start_time' => trim($startTime), 'end_time' => trim($endTime), 'duration' => $this->calculateDuration(trim($startTime), trim($endTime)) ]; } return [ 'start_time' => $timeSlot, 'end_time' => '', 'duration' => 60 // 默认1小时 ]; } /** * 计算时长(分钟) * @param string $startTime 开始时间 * @param string $endTime 结束时间 * @return int 时长(分钟) */ private function calculateDuration($startTime, $endTime) { try { $start = strtotime($startTime); $end = strtotime($endTime); return ($end - $start) / 60; } catch (\Exception $e) { return 60; // 默认1小时 } } /** * 获取课程安排的学员信息 * @param int $scheduleId 课程安排ID * @return array 学员信息数组 */ private function getScheduleStudents($scheduleId) { try { $students = Db::name('person_course_schedule') ->alias('pcs') ->leftJoin($this->prefix . 'student s', 'pcs.student_id = s.id') ->leftJoin($this->prefix . 'customer_resources cr', 'pcs.resources_id = cr.id') ->leftJoin($this->prefix . 'member m', 'cr.member_id = m.member_id') ->where('pcs.schedule_id', $scheduleId) ->where('pcs.status', '<>', 2) // 排除请假的学员,包含待上课(0)和已上课(1) ->where('pcs.deleted_at', 0) ->field([ 'pcs.*', 's.name as student_name', 'cr.name as resource_name', 'cr.phone_number', 'cr.age', 'cr.gender', 'm.headimg as avatar' ]) ->select() ->toArray(); foreach ($students as &$student) { $student['name'] = $student['student_name'] ?: $student['resource_name']; $student['avatar'] = $student['avatar'] ? $this->formatImageUrl($student['avatar']) : ''; $student['status_text'] = $this->getStudentStatusText($student['status']); $student['person_type_text'] = $student['person_type'] == 'student' ? '正式学员' : '体验学员'; $student['course_type_text'] = $this->getCourseTypeText($student['course_type']); } return $students; } catch (\Exception $e) { return []; } } /** * 获取助教信息 * @param string $assistantIds 助教ID字符串,使用逗号分隔 * @return array 助教信息数组 */ private function getScheduleAssistants($assistantIds) { if (empty($assistantIds)) { return []; } try { $ids = explode(',', $assistantIds); $assistants = Db::name('personnel') ->whereIn('id', $ids) ->field('id, name, head_img, phone') ->select() ->toArray(); foreach ($assistants as &$assistant) { $assistant['head_img'] = $assistant['head_img'] ? $this->formatImageUrl($assistant['head_img']) : ''; } return $assistants; } catch (\Exception $e) { return []; } } /** * 获取状态文本 * @param string $status 状态码 * @return string 状态文本描述 */ private function getStatusText($status) { $statusMap = [ 'pending' => '待开始', 'upcoming' => '即将开始', 'ongoing' => '进行中', 'completed' => '已结束' ]; return $statusMap[$status] ?? $status; } /** * 获取学员状态文本 * @param int $status 学员状态码 * @return string 学员状态文本描述 */ private function getStudentStatusText($status) { $statusMap = [ 0 => '待上课', 1 => '已上课', 2 => '请假' ]; return $statusMap[$status] ?? '未知'; } /** * 获取课程类型文本 * @param int $courseType 课程类型码 * @return string 课程类型文本描述 */ private function getCourseTypeText($courseType) { $typeMap = [ 1 => '加课', 2 => '补课', 3 => '等待位' ]; return $typeMap[$courseType] ?? '正常课程'; } /** * 格式化图片URL * @param string $imagePath 图片路径 * @return string 格式化后的图片URL */ private function formatImageUrl($imagePath) { if (empty($imagePath)) { return ''; } // 如果已经是完整URL,直接返回 if (strpos($imagePath, 'http') === 0) { return $imagePath; } // 拼接域名 $domain = request()->domain(); return $domain . '/' . ltrim($imagePath, '/'); } /** * 获取筛选选项(教练、课程、班级等) * @param array $data 请求参数 * @return array 筛选选项数据 */ public function getFilterOptions($data = []) { try { $result = [ 'coaches' => [], // 教练列表 'courses' => [], // 课程列表 'classes' => [], // 班级列表 'venues' => [], // 场地列表 'campuses' => [], // 校区列表 'status_options' => [] // 状态选项 ]; // 获取教练列表(基于教练部门dept_id=23) $result['coaches'] = $this->getCoachListWithPermission(); // 获取课程列表 $result['courses'] = Db::name('course') ->where('deleted_at', 0) ->field('id, course_name, course_type, duration') ->select() ->toArray(); // 获取班级列表 $result['classes'] = Db::name('class') ->where('deleted_at', 0) ->field('id, class_name, age_group, status') ->select() ->toArray(); // 获取场地列表(基于校区权限) $result['venues'] = $this->getVenueListWithPermission(); // 获取校区列表 $result['campuses'] = Db::name('campus') ->where('delete_time', 0) ->field('id, campus_name, campus_address') ->select() ->toArray(); // 状态选项 $result['status_options'] = [ ['value' => 'pending', 'label' => '待开始'], ['value' => 'upcoming', 'label' => '即将开始'], ['value' => 'ongoing', 'label' => '进行中'], ['value' => 'completed', 'label' => '已结束'] ]; return $result; } catch (\Exception $e) { return [ 'coaches' => [], 'courses' => [], 'classes' => [], 'venues' => [], 'campuses' => [], 'status_options' => [], 'error' => $e->getMessage() ]; } } /** * 获取课程安排详情 * @param int $scheduleId 课程安排ID * @return array 课程安排详细信息或错误信息 */ public function getScheduleInfo($scheduleId) { try { // 查询课程安排信息 $schedule = Db::name('course_schedule') ->alias('cs') ->leftJoin($this->prefix . 'course c', 'cs.course_id = c.id') ->leftJoin($this->prefix . 'venue v', 'cs.venue_id = v.id') ->leftJoin($this->prefix . 'campus cap', 'cs.campus_id = cap.id') ->leftJoin($this->prefix . 'personnel coach', 'cs.coach_id = coach.id') ->leftJoin($this->prefix . 'personnel edu', 'cs.education_id = edu.id') ->where('cs.id', $scheduleId) ->where('cs.deleted_at', 0) ->field([ 'cs.*', 'c.course_name', 'c.course_type', 'c.duration as course_duration', 'c.session_count', 'c.single_session_count', 'v.venue_name', 'v.capacity as venue_capacity', 'cap.campus_name', 'coach.name as coach_name', 'coach.head_img as coach_avatar', 'edu.name as education_name' ]) ->find(); if (empty($schedule)) { return ['code' => 0, 'msg' => '课程安排不存在或已被删除']; } // 解析时间段 $schedule['time_info'] = $this->parseTimeSlot($schedule['time_slot']); // 获取参与学员信息 $schedule['students'] = $this->getScheduleStudents($schedule['id']); // 获取助教信息 $schedule['assistants'] = $this->getScheduleAssistants($schedule['assistant_ids']); // 计算已报名人数 $schedule['enrolled_count'] = count($schedule['students']); // 计算剩余容量 $schedule['remaining_capacity'] = max(0, ($schedule['available_capacity'] ?? $schedule['venue_capacity']) - $schedule['enrolled_count']); // 格式化状态 $schedule['status_text'] = $this->getStatusText($schedule['status']); // 格式化创建方式 $schedule['created_by_text'] = $schedule['created_by'] == 'manual' ? '手动安排' : '系统创建'; // 处理图片路径 $schedule['coach_avatar'] = $schedule['coach_avatar'] ? $this->formatImageUrl($schedule['coach_avatar']) : ''; // 获取班级相关信息 if (!empty($schedule['class_id'])) { $schedule['class_info'] = Db::name('class') ->where('id', $schedule['class_id']) ->field('id, class_name, age_group, status') ->find(); } else { $schedule['class_info'] = null; } // 获取历史变更记录(注释掉,表不存在) // $schedule['change_history'] = Db::name('course_schedule_changes') // ->where('schedule_id', $scheduleId) // ->order('created_at DESC') // ->select() // ->toArray(); $schedule['change_history'] = []; return $schedule; } catch (\Exception $e) { return ['code' => 0, 'msg' => $e->getMessage()]; } } /** * 获取场地列表(用于添加课程安排) * @param array $data * @return array */ public function getVenueListForSchedule(array $data) { try { $where = []; // 场地名称关键词搜索 if (!empty($data['keyword'])) { $where[] = ['venue_name', 'like', '%' . $data['keyword'] . '%']; } // 校区筛选 if (!empty($data['campus_id'])) { $where[] = ['campus_id', '=', $data['campus_id']]; } // 如果没传校区 id 权限里有校区 id 则使用权限内的班级数据 if (empty($data['campus_id']) && $this->campus_id) { $where[] = ['campus_id', '=', $this->campus_id]; } // 状态筛选,默认获取可用场地 if (isset($data['status'])) { $where[] = ['availability_status', '=', $data['status']]; } // 只获取未逻辑删除的场地 $where[] = ['deleted_at', '=', 0]; $venueList = Db::name('venue') ->where($where) ->field('id, venue_name, capacity, availability_status, time_range_type, time_range_start, time_range_end, fixed_time_ranges') ->order('created_at DESC') ->select() ->toArray(); return [ 'code' => 1, 'msg' => '获取成功', 'data' => $venueList ]; } catch (\Exception $e) { return [ 'code' => 0, 'msg' => '获取场地列表失败:' . $e->getMessage(), 'data' => [] ]; } } /** * 获取场地可用时间段 * @param array $data * @return array */ public function getVenueAvailableTimeSlots(array $data) { try { $venueId = $data['venue_id']; $date = $data['date']; // 获取场地信息 $venue = Db::name('venue') ->where('id', $venueId) ->where('availability_status', 1) ->find(); if (empty($venue)) { return [ 'code' => 0, 'msg' => '场地不存在或不可用', 'data' => [] ]; } // 根据场地时间类型获取可用时间段 $availableSlots = $this->generateTimeSlots($venue, $date); // 获取该场地该日期已安排的时间段 $occupiedSlots = Db::name('course_schedule') ->where('venue_id', $venueId) ->where('course_date', $date) ->where('deleted_at', 0) ->column('time_slot'); // 过滤已占用的时间段 $availableSlots = array_filter($availableSlots, function($slot) use ($occupiedSlots) { return !in_array($slot['time_slot'], $occupiedSlots); }); return [ 'code' => 1, 'msg' => '获取成功', 'data' => array_values($availableSlots) ]; } catch (\Exception $e) { return [ 'code' => 0, 'msg' => '获取场地可用时间失败:' . $e->getMessage(), 'data' => [] ]; } } /** * 创建课程安排 * @param array $data * @return array */ public function createCourseSchedule(array $data) { try { // 开启事务 Db::startTrans(); // 验证场地时间冲突 $conflictCheck = $this->checkVenueConflict($data['venue_id'], $data['course_date'], $data['time_slot']); if (!$conflictCheck['code']) { Db::rollback(); return $conflictCheck; } // 验证教练时间冲突 $coachConflictCheck = $this->checkCoachConflict($data['coach_id'], $data['course_date'], $data['time_slot']); if (!$coachConflictCheck['code']) { Db::rollback(); return $coachConflictCheck; } // 准备插入数据 $insertData = [ 'campus_id' => $data['campus_id'], 'venue_id' => $data['venue_id'], 'course_date' => $data['course_date'], 'time_slot' => $data['time_slot'], 'course_id' => $data['course_id'], 'coach_id' => $data['coach_id'], 'available_capacity' => $data['available_capacity'], 'status' => 'pending', 'created_by' => $data['created_by'] ?? 'manual', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'deleted_at' => 0 ]; // 如果传入了班级ID,则添加到插入数据中 if (!empty($data['class_id'])) { $insertData['class_id'] = $data['class_id']; } // 如果有备注,则添加 if (!empty($data['remarks'])) { $insertData['remarks'] = $data['remarks']; } // 插入课程安排 $scheduleId = Db::name('course_schedule')->insertGetId($insertData); if (!$scheduleId) { Db::rollback(); return [ 'code' => 0, 'msg' => '创建课程安排失败' ]; } // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => '创建成功', 'data' => [ 'schedule_id' => $scheduleId ] ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '创建课程安排失败:' . $e->getMessage() ]; } } /** * 生成时间段选项 * @param array $venue * @param string $date * @return array */ private function generateTimeSlots($venue, $date) { $slots = []; switch ($venue['time_range_type']) { case 'range': // 范围类型:从开始时间到结束时间,每小时一个时间段 $startTime = strtotime($venue['time_range_start']); $endTime = strtotime($venue['time_range_end']); for ($time = $startTime; $time < $endTime; $time += 3600) { $startTimeStr = date('H:i', $time); $endTimeStr = date('H:i', $time + 3600); $slots[] = [ 'time_slot' => $startTimeStr . '-' . $endTimeStr, 'start_time' => $startTimeStr, 'end_time' => $endTimeStr ]; } break; case 'fixed': // 固定时间范围类型 if (!empty($venue['fixed_time_ranges'])) { $fixedRanges = json_decode($venue['fixed_time_ranges'], true); if (is_array($fixedRanges)) { foreach ($fixedRanges as $range) { // 兼容不同的字段名格式 $startTime = $range['start_time'] ?? $range['start'] ?? ''; $endTime = $range['end_time'] ?? $range['end'] ?? ''; if ($startTime && $endTime) { $slots[] = [ 'time_slot' => $startTime . '-' . $endTime, 'start_time' => $startTime, 'end_time' => $endTime ]; } } } } break; case 'all': // 全天可用,生成默认时间段(8:00-22:00) for ($hour = 8; $hour < 22; $hour++) { $startTimeStr = str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00'; $endTimeStr = str_pad($hour + 1, 2, '0', STR_PAD_LEFT) . ':00'; $slots[] = [ 'time_slot' => $startTimeStr . '-' . $endTimeStr, 'start_time' => $startTimeStr, 'end_time' => $endTimeStr ]; } break; } return $slots; } /** * 检查场地时间冲突 * @param int $venueId * @param string $date * @param string $timeSlot * @return array */ private function checkVenueConflict($venueId, $date, $timeSlot) { $conflict = Db::name('course_schedule') ->where('venue_id', $venueId) ->where('course_date', $date) ->where('time_slot', $timeSlot) ->where('deleted_at', 0) ->find(); if ($conflict) { return [ 'code' => 0, 'msg' => '该场地在该时间段已有课程安排' ]; } return ['code' => 1]; } /** * 检查教练时间冲突 * @param int $coachId * @param string $date * @param string $timeSlot * @return array */ public function checkCoachConflict($coachId, $date, $timeSlot) { $conflict = Db::name('course_schedule') ->where('coach_id', $coachId) ->where('course_date', $date) ->where('time_slot', $timeSlot) ->where('deleted_at', 0) ->find(); if ($conflict) { return [ 'code' => 0, 'msg' => '该教练在该时间段已有课程安排' ]; } return ['code' => 1]; } /** * 获取教练列表(基于教练部门权限) * @return array 教练列表 */ private function getCoachListWithPermission() { try { $query = Db::name('personnel') ->alias('p') ->join($this->prefix . 'campus_person_role cpr', 'p.id = cpr.person_id') ->join($this->prefix . 'sys_role sr', 'cpr.role_id = sr.role_id') ->where('sr.dept_id', 23) // 教练部门 ->where('p.deleted_at', 0) ->field('p.id, p.name, p.head_img as avatar, p.phone'); // 如果当前用户有校区权限,则只显示同校区的教练 if (!empty($this->campus_id)) { $query->where('cpr.campus_id', $this->campus_id); } $coaches = $query->group('p.id') ->select() ->toArray(); // 处理头像路径 foreach ($coaches as &$coach) { $coach['avatar'] = $coach['avatar'] ? $this->formatImageUrl($coach['avatar']) : ''; } return $coaches; } catch (\Exception $e) { return []; } } /** * 获取场地列表(基于校区权限) * @return array 场地列表 */ private function getVenueListWithPermission() { try { $query = Db::name('venue') ->where('deleted_at', 0) ->where('availability_status', 1) // 只获取可用场地 ->field('id, venue_name, capacity, time_range_type, time_range_start, time_range_end, fixed_time_ranges, campus_id'); // 如果当前用户有校区权限,则只显示同校区的场地 if (!empty($this->campus_id)) { $query->where('campus_id', $this->campus_id); } return $query->select()->toArray(); } catch (\Exception $e) { return []; } } /** * 更新课程安排 * @param array $data 更新数据 * @return array 更新结果 */ public function updateSchedule(array $data) { try { // 验证必填字段 if (empty($data['schedule_id'])) { return [ 'code' => 0, 'msg' => '课程安排ID不能为空' ]; } // 开启事务 Db::startTrans(); $scheduleId = $data['schedule_id']; // 查询当前课程安排信息 $currentSchedule = Db::name('course_schedule') ->where('id', $scheduleId) ->where('deleted_at', 0) ->find(); if (empty($currentSchedule)) { Db::rollback(); return [ 'code' => 0, 'msg' => '课程安排不存在或已被删除' ]; } // 准备更新数据 $updateData = [ 'updated_at' => date('Y-m-d H:i:s') ]; // 如果修改了场地或时间,需要检查冲突 $needCheckConflict = false; if (isset($data['venue_id']) && $data['venue_id'] != $currentSchedule['venue_id']) { $updateData['venue_id'] = $data['venue_id']; $needCheckConflict = true; } if (isset($data['course_date']) && $data['course_date'] != $currentSchedule['course_date']) { $updateData['course_date'] = $data['course_date']; $needCheckConflict = true; } if (isset($data['time_slot']) && $data['time_slot'] != $currentSchedule['time_slot']) { $updateData['time_slot'] = $data['time_slot']; $needCheckConflict = true; } // 如果修改了教练,需要检查教练时间冲突 $needCheckCoachConflict = false; if (isset($data['coach_id']) && $data['coach_id'] != $currentSchedule['coach_id']) { $updateData['coach_id'] = $data['coach_id']; $needCheckCoachConflict = true; } // 检查场地时间冲突 if ($needCheckConflict) { $venueId = $updateData['venue_id'] ?? $currentSchedule['venue_id']; $courseDate = $updateData['course_date'] ?? $currentSchedule['course_date']; $timeSlot = $updateData['time_slot'] ?? $currentSchedule['time_slot']; $conflictCheck = $this->checkVenueConflictForUpdate($venueId, $courseDate, $timeSlot, $scheduleId); if (!$conflictCheck['code']) { Db::rollback(); return $conflictCheck; } } // 检查教练时间冲突 if ($needCheckCoachConflict || $needCheckConflict) { $coachId = $updateData['coach_id'] ?? $currentSchedule['coach_id']; $courseDate = $updateData['course_date'] ?? $currentSchedule['course_date']; $timeSlot = $updateData['time_slot'] ?? $currentSchedule['time_slot']; $coachConflictCheck = $this->checkCoachConflictForUpdate($coachId, $courseDate, $timeSlot, $scheduleId); if (!$coachConflictCheck['code']) { Db::rollback(); return $coachConflictCheck; } } // 其他可更新字段 if (isset($data['available_capacity'])) { $updateData['available_capacity'] = $data['available_capacity']; } if (isset($data['campus_id'])) { $updateData['campus_id'] = $data['campus_id']; } if (isset($data['course_id'])) { $updateData['course_id'] = $data['course_id']; } if (isset($data['class_id'])) { $updateData['class_id'] = $data['class_id']; } if (isset($data['education_id'])) { $updateData['education_id'] = $data['education_id']; } if (isset($data['remarks'])) { $updateData['remarks'] = $data['remarks']; } if (isset($data['status'])) { $updateData['status'] = $data['status']; } // 如果没有实际的更新数据,返回成功 if (count($updateData) <= 1) { // 只有 updated_at Db::commit(); return [ 'code' => 1, 'msg' => '更新成功', 'data' => ['schedule_id' => $scheduleId] ]; } // 执行更新 $result = Db::name('course_schedule') ->where('id', $scheduleId) ->update($updateData); if ($result === false) { Db::rollback(); return [ 'code' => 0, 'msg' => '更新失败' ]; } // 记录变更历史(可选) $this->recordScheduleChange($scheduleId, $currentSchedule, $updateData); // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => '更新成功', 'data' => ['schedule_id' => $scheduleId] ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '更新课程安排失败:' . $e->getMessage() ]; } } /** * 检查场地时间冲突(更新时排除自身) * @param int $venueId 场地ID * @param string $date 日期 * @param string $timeSlot 时间段 * @param int $excludeScheduleId 排除的课程安排ID * @return array */ private function checkVenueConflictForUpdate($venueId, $date, $timeSlot, $excludeScheduleId) { $conflict = Db::name('course_schedule') ->where('venue_id', $venueId) ->where('course_date', $date) ->where('time_slot', $timeSlot) ->where('id', '<>', $excludeScheduleId) ->where('deleted_at', 0) ->find(); if ($conflict) { return [ 'code' => 0, 'msg' => '该场地在该时间段已有其他课程安排' ]; } return ['code' => 1]; } /** * 检查教练时间冲突(更新时排除自身) * @param int $coachId 教练ID * @param string $date 日期 * @param string $timeSlot 时间段 * @param int $excludeScheduleId 排除的课程安排ID * @return array */ private function checkCoachConflictForUpdate($coachId, $date, $timeSlot, $excludeScheduleId) { $conflict = Db::name('course_schedule') ->where('coach_id', $coachId) ->where('course_date', $date) ->where('time_slot', $timeSlot) ->where('id', '<>', $excludeScheduleId) ->where('deleted_at', 0) ->find(); if ($conflict) { return [ 'code' => 0, 'msg' => '该教练在该时间段已有其他课程安排' ]; } return ['code' => 1]; } /** * 记录课程安排变更历史 * @param int $scheduleId 课程安排ID * @param array $oldData 原始数据 * @param array $newData 新数据 * @return void */ private function recordScheduleChange($scheduleId, $oldData, $newData) { try { $changes = []; // 比较字段变化 foreach ($newData as $field => $newValue) { if ($field == 'updated_at') continue; $oldValue = $oldData[$field] ?? null; if ($oldValue != $newValue) { $changes[] = [ 'field' => $field, 'old_value' => $oldValue, 'new_value' => $newValue ]; } } if (!empty($changes)) { Db::name('course_schedule_changes')->insert([ 'schedule_id' => $scheduleId, 'changes' => json_encode($changes), 'changed_by' => $this->user_id ?? 0, 'changed_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s') ]); } } catch (\Exception $e) { // 记录日志,但不影响主流程 trace($e->getMessage()); } } /** * 根据场地生成可用时间选项 * @param array $venue 场地信息 * @return array 时间选项列表 */ public function generateVenueTimeOptions($venue) { $timeOptions = []; switch ($venue['time_range_type']) { case 'fixed': // 固定时间段 if (!empty($venue['fixed_time_ranges'])) { $fixedRanges = json_decode($venue['fixed_time_ranges'], true); if (is_array($fixedRanges)) { foreach ($fixedRanges as $range) { $startTime = $range['start_time'] ?? ''; $endTime = $range['end_time'] ?? ''; if ($startTime && $endTime) { $timeOptions[] = [ 'value' => $startTime . '-' . $endTime, 'text' => $startTime . '-' . $endTime ]; } } } } break; case 'range': // 时间范围 if (!empty($venue['time_range_start']) && !empty($venue['time_range_end'])) { $start = strtotime($venue['time_range_start']); $end = strtotime($venue['time_range_end']); // 每小时生成一个时间段 for ($time = $start; $time < $end; $time += 3600) { $startTimeStr = date('H:i', $time); $endTimeStr = date('H:i', $time + 3600); $timeOptions[] = [ 'value' => $startTimeStr . '-' . $endTimeStr, 'text' => $startTimeStr . '-' . $endTimeStr ]; } } break; case 'all': default: // 全天可用,默认8:30开始,每小时一档 $start = strtotime('08:30'); $end = strtotime('22:00'); // 每小时生成一个时间段,保持30分钟对齐 for ($time = $start; $time < $end; $time += 3600) { $startTimeStr = date('H:i', $time); $endTimeStr = date('H:i', $time + 3600); $timeOptions[] = [ 'value' => $startTimeStr . '-' . $endTimeStr, 'text' => $startTimeStr . '-' . $endTimeStr ]; } break; } return $timeOptions; } /** * 批量创建课程安排 * @param array $data 批量创建数据 * @return array 创建结果 */ public function batchCreateSchedule(array $data) { try { // 验证必填字段 if (empty($data['schedules']) || !is_array($data['schedules'])) { return [ 'code' => 0, 'msg' => '课程安排数据不能为空' ]; } // 开启事务 Db::startTrans(); $successCount = 0; $failedCount = 0; $errors = []; foreach ($data['schedules'] as $index => $schedule) { $result = $this->createCourseSchedule($schedule); if ($result['code']) { $successCount++; } else { $failedCount++; $errors[] = "第" . ($index + 1) . "条: " . $result['msg']; } } if ($failedCount > 0) { Db::rollback(); return [ 'code' => 0, 'msg' => "批量创建失败,成功:{$successCount}条,失败:{$failedCount}条", 'data' => [ 'success_count' => $successCount, 'failed_count' => $failedCount, 'errors' => $errors ] ]; } // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => "批量创建成功,共创建{$successCount}条课程安排", 'data' => [ 'success_count' => $successCount, 'failed_count' => $failedCount ] ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '批量创建课程安排失败:' . $e->getMessage() ]; } } /** * 删除课程安排 * @param int $scheduleId 课程安排ID * @return array 删除结果 */ public function deleteSchedule($scheduleId) { try { if (empty($scheduleId)) { return [ 'code' => 0, 'msg' => '课程安排ID不能为空' ]; } // 开启事务 Db::startTrans(); // 查询课程安排是否存在 $schedule = Db::name('course_schedule') ->where('id', $scheduleId) ->where('deleted_at', 0) ->find(); if (empty($schedule)) { Db::rollback(); return [ 'code' => 0, 'msg' => '课程安排不存在或已被删除' ]; } // 检查是否有学员已报名 $hasStudents = Db::name('person_course_schedule') ->where('schedule_id', $scheduleId) ->where('deleted_at', 0) ->count(); if ($hasStudents > 0) { Db::rollback(); return [ 'code' => 0, 'msg' => '该课程安排已有学员报名,无法删除' ]; } // 软删除课程安排 $result = Db::name('course_schedule') ->where('id', $scheduleId) ->update([ 'deleted_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]); if ($result === false) { Db::rollback(); return [ 'code' => 0, 'msg' => '删除失败' ]; } // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => '删除成功' ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '删除课程安排失败:' . $e->getMessage() ]; } } /** * 获取课程安排统计 * @param array $data 统计参数 * @return array 统计结果 */ public function getScheduleStatistics($data = []) { try { $where = $this->buildScheduleWhere($data); // 总课程安排数 $totalSchedules = Db::name('course_schedule') ->alias('cs') ->where($where) ->where('cs.deleted_at', 0) ->count(); // 按状态统计 $statusStats = Db::name('course_schedule') ->alias('cs') ->where($where) ->where('cs.deleted_at', 0) ->field('status, COUNT(*) as count') ->group('status') ->select() ->toArray(); // 按教练统计 $coachStats = Db::name('course_schedule') ->alias('cs') ->leftJoin('school_personnel p', 'cs.coach_id = p.id') ->where($where) ->where('cs.deleted_at', 0) ->field('cs.coach_id, p.name as coach_name, COUNT(*) as count') ->group('cs.coach_id') ->order('count DESC') ->limit(10) ->select() ->toArray(); // 按场地统计 $venueStats = Db::name('course_schedule') ->alias('cs') ->leftJoin('school_venue v', 'cs.venue_id = v.id') ->where($where) ->where('cs.deleted_at', 0) ->field('cs.venue_id, v.venue_name, COUNT(*) as count') ->group('cs.venue_id') ->order('count DESC') ->limit(10) ->select() ->toArray(); // 按日期统计(最近7天) $dateStats = []; for ($i = 6; $i >= 0; $i--) { $date = date('Y-m-d', strtotime("-{$i} days")); $count = Db::name('course_schedule') ->alias('cs') ->where($where) ->where('cs.course_date', $date) ->where('cs.deleted_at', 0) ->count(); $dateStats[] = [ 'date' => $date, 'count' => $count ]; } return [ 'total_schedules' => $totalSchedules, 'status_stats' => $statusStats, 'coach_stats' => $coachStats, 'venue_stats' => $venueStats, 'date_stats' => $dateStats ]; } catch (\Exception $e) { return [ 'total_schedules' => 0, 'status_stats' => [], 'coach_stats' => [], 'venue_stats' => [], 'date_stats' => [], 'error' => $e->getMessage() ]; } } /** * 学员加入课程安排 * @param array $data 加入数据 * @return array 加入结果 */ public function joinSchedule(array $data) { try { // 验证必填字段 if (empty($data['schedule_id']) || empty($data['student_id'])) { return [ 'code' => 0, 'msg' => '课程安排ID和学员ID不能为空' ]; } // 开启事务 Db::startTrans(); $scheduleId = $data['schedule_id']; $studentId = $data['student_id']; // 查询课程安排信息 $schedule = Db::name('course_schedule') ->where('id', $scheduleId) ->where('deleted_at', 0) ->find(); if (empty($schedule)) { Db::rollback(); return [ 'code' => 0, 'msg' => '课程安排不存在或已被删除' ]; } // 检查学员是否已经报名 $exists = Db::name('person_course_schedule') ->where('schedule_id', $scheduleId) ->where('student_id', $studentId) ->where('deleted_at', 0) ->find(); if ($exists) { Db::rollback(); return [ 'code' => 0, 'msg' => '学员已经报名该课程安排' ]; } // 检查课程容量 $enrolledCount = Db::name('person_course_schedule') ->where('schedule_id', $scheduleId) ->where('deleted_at', 0) ->count(); if ($enrolledCount >= $schedule['available_capacity']) { Db::rollback(); return [ 'code' => 0, 'msg' => '课程容量已满,无法报名' ]; } // 插入报名记录 $insertData = [ 'schedule_id' => $scheduleId, 'student_id' => $studentId, 'person_type' => 'student', 'course_type' => $data['course_type'] ?? 0, 'resources_id' => $data['resources_id'] ?? 0, 'status' => 0, // 待上课 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'deleted_at' => 0 ]; $result = Db::name('person_course_schedule')->insert($insertData); if (!$result) { Db::rollback(); return [ 'code' => 0, 'msg' => '报名失败' ]; } // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => '报名成功', 'data' => [ 'schedule_id' => $scheduleId, 'student_id' => $studentId ] ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '学员加入课程安排失败:' . $e->getMessage() ]; } } /** * 学员退出课程安排 * @param array $data 退出数据 * @return array 退出结果 */ public function leaveSchedule(array $data) { try { // 验证必填字段 if (empty($data['schedule_id']) || empty($data['student_id'])) { return [ 'code' => 0, 'msg' => '课程安排ID和学员ID不能为空' ]; } // 开启事务 Db::startTrans(); $scheduleId = $data['schedule_id']; $studentId = $data['student_id']; // 查询报名记录 $enrollment = Db::name('person_course_schedule') ->where('schedule_id', $scheduleId) ->where('student_id', $studentId) ->where('deleted_at', 0) ->find(); if (empty($enrollment)) { Db::rollback(); return [ 'code' => 0, 'msg' => '学员未报名该课程安排' ]; } // 检查是否已经上课 if ($enrollment['status'] == 1) { Db::rollback(); return [ 'code' => 0, 'msg' => '学员已经上课,无法退出' ]; } // 软删除报名记录 $result = Db::name('person_course_schedule') ->where('id', $enrollment['id']) ->update([ 'deleted_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'remark' => $data['remark'] ?? '学员主动退出' ]); if ($result === false) { Db::rollback(); return [ 'code' => 0, 'msg' => '退出失败' ]; } // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => '退出成功' ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '学员退出课程安排失败:' . $e->getMessage() ]; } } /** * 升级等待位学员为正式学员 * @param array $data 升级数据 * @return array 升级结果 */ public function upgradeStudentSchedule(array $data) { try { // 验证必填字段 if (empty($data['resources_id']) || empty($data['schedule_id'])) { return [ 'code' => 0, 'msg' => '课程安排ID和资源ID不能为空' ]; } if (empty($data['from_schedule_type']) || empty($data['to_schedule_type'])) { return [ 'code' => 0, 'msg' => '升级类型参数不完整' ]; } // 验证升级方向(只能从等待位升级到正式位) if ($data['from_schedule_type'] != 2 || $data['to_schedule_type'] != 1) { return [ 'code' => 0, 'msg' => '只能从等待位升级到正式位' ]; } // 开启事务 Db::startTrans(); $scheduleId = $data['schedule_id']; $resourcesId = $data['resources_id']; // 查询课程安排信息 $schedule = Db::name('course_schedule') ->where('id', $scheduleId) ->where('deleted_at', 0) ->find(); if (empty($schedule)) { Db::rollback(); return [ 'code' => 0, 'msg' => '课程安排不存在' ]; } // 查询等待位学员记录 $enrollment = Db::name('person_course_schedule') ->where('schedule_id', $scheduleId) ->where('resources_id', $resourcesId) ->where('schedule_type', 2) // 等待位 ->where('deleted_at', 0) ->find(); if (empty($enrollment)) { Db::rollback(); return [ 'code' => 0, 'msg' => '找不到等待位学员记录' ]; } // 检查正式位是否已满 $formalCount = Db::name('person_course_schedule') ->where('schedule_id', $scheduleId) ->where('schedule_type', 1) // 正式位 ->where('deleted_at', 0) ->count(); $maxStudents = intval($schedule['max_students']); if ($maxStudents > 0 && $formalCount >= $maxStudents) { Db::rollback(); return [ 'code' => 0, 'msg' => '正式位已满,无法升级' ]; } // 更新学员记录 $updateData = [ 'schedule_type' => 1, // 升级为正式位 'updated_at' => date('Y-m-d H:i:s'), 'remark' => ($data['remark'] ?? '') . ' [从等待位升级]' ]; // 如果课程类型是等待位专用类型(3),改为正式课(1) if ($enrollment['course_type'] == 3) { $updateData['course_type'] = $data['course_type'] ?? 1; } $result = Db::name('person_course_schedule') ->where('id', $enrollment['id']) ->update($updateData); if ($result === false) { Db::rollback(); return [ 'code' => 0, 'msg' => '升级失败' ]; } // 提交事务 Db::commit(); return [ 'code' => 1, 'msg' => '升级成功', 'data' => [ 'enrollment_id' => $enrollment['id'], 'new_position' => $formalCount + 1 ] ]; } catch (\Exception $e) { Db::rollback(); return [ 'code' => 0, 'msg' => '学员升级失败:' . $e->getMessage() ]; } } }