checkStudentPermission($studentId); // 构建查询条件 $where = [ ['cs.deleted_at', '=', 0], ['cs.status', '=', 'pending'], // 待开始的课程 ['cs.course_date', '>=', date('Y-m-d')] // 未来的课程 ]; // 日期筛选 if (!empty($params['date'])) { $where[] = ['cs.course_date', '=', $params['date']]; } // 日期范围筛选 if (!empty($params['start_date']) && !empty($params['end_date'])) { $where[] = ['cs.course_date', 'between', [$params['start_date'], $params['end_date']]]; } // 教练筛选 if (!empty($params['coach_id'])) { $where[] = ['cs.coach_id', '=', $params['coach_id']]; } // 场地筛选 if (!empty($params['venue_id'])) { $where[] = ['cs.venue_id', '=', $params['venue_id']]; } // 查询可预约的课程安排 $availableCourses = Db::table('school_course_schedule cs') ->leftJoin('school_course c', 'cs.course_id = c.id') ->leftJoin('school_personnel p', 'cs.coach_id = p.id') ->leftJoin('school_venue v', 'cs.venue_id = v.id') ->where($where) ->field(' cs.id, cs.course_date, cs.time_slot, COALESCE(cs.start_time, CASE WHEN cs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(cs.time_slot, "-", 1) ELSE "09:00" END ) as start_time, COALESCE(cs.end_time, CASE WHEN cs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(cs.time_slot, "-", -1) ELSE "10:00" END ) as end_time, cs.max_students, cs.available_capacity, c.course_name, c.course_type, c.duration, p.name as coach_name, v.venue_name, cs.status ') ->order('cs.course_date asc, cs.start_time asc') ->select() ->toArray(); // 处理每个课程的预约状态 foreach ($availableCourses as &$course) { // 计算已预约人数 $bookedCount = Db::table('school_person_course_schedule') ->where('schedule_id', $course['id']) ->where('deleted_at', 0) ->where('status', 0) // 0-待上课 ->count(); $course['current_students'] = $bookedCount; $course['max_students'] = $course['max_students'] ?: 10; // 默认最大10人 // 检查该学员是否已预约此时段 $isBooked = Db::table('school_person_course_schedule') ->where('student_id', $studentId) ->where('schedule_id', $course['id']) ->where('deleted_at', 0) ->where('status', '!=', 3) // 3-取消 ->find(); // 确定课程状态 if ($isBooked) { $course['booking_status'] = 'booked'; } elseif ($bookedCount >= $course['max_students']) { $course['booking_status'] = 'full'; } else { $course['booking_status'] = 'available'; } // 计算时长 $course['duration'] = 60; // 默认60分钟 } return [ 'list' => $availableCourses, 'total' => count($availableCourses) ]; } /** * 创建课程预约 * @param array $data * @return array */ public function createBooking($data) { $studentId = $data['student_id']; $scheduleId = $data['schedule_id']; // 验证学员权限 $this->checkStudentPermission($studentId); // 检查课程安排是否存在 $courseSchedule = Db::table('school_course_schedule') ->where('id', $scheduleId) ->where('deleted_at', 0) ->find(); if (!$courseSchedule) { throw new CommonException('课程安排不存在'); } // 检查预约冲突 $conflictCheck = $this->checkBookingConflict([ 'student_id' => $studentId, 'booking_date' => $data['course_date'], 'time_slot' => $data['time_slot'] ]); if ($conflictCheck['has_conflict']) { throw new CommonException('该时段已有预约冲突'); } // 检查课程容量 $bookedCount = Db::table('school_person_course_schedule') ->where('schedule_id', $scheduleId) ->where('deleted_at', 0) ->where('status', '!=', 3) // 非取消状态 ->count(); $maxStudents = $courseSchedule['max_students'] ?: 10; if ($bookedCount >= $maxStudents) { throw new CommonException('该课程已满员'); } // 检查是否已预约过 $existingBooking = Db::table('school_person_course_schedule') ->where('student_id', $studentId) ->where('schedule_id', $scheduleId) ->where('deleted_at', 0) ->where('status', '!=', 3) ->find(); if ($existingBooking) { throw new CommonException('您已预约过此课程'); } // 创建预约记录 $bookingData = [ 'student_id' => $studentId, 'schedule_id' => $scheduleId, 'course_date' => $data['course_date'], 'time_slot' => $data['time_slot'], 'person_type' => 'student', 'course_type' => 3, // 3-预约课程 'status' => 0, // 0-待上课 'remark' => $data['note'] ?? '', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'deleted_at' => 0 ]; try { $bookingId = Db::table('school_person_course_schedule')->insertGetId($bookingData); if (!$bookingId) { throw new CommonException('预约创建失败'); } // TODO: 发送预约成功消息通知 return [ 'booking_id' => $bookingId, 'status' => 'success', 'message' => '预约创建成功' ]; } catch (\Exception $e) { throw new CommonException('预约创建失败:' . $e->getMessage()); } } /** * 获取我的预约列表 * @param array $params * @return array */ public function getMyBookingList($params) { $studentId = $params['student_id']; // 验证学员权限 $this->checkStudentPermission($studentId); // 构建查询条件 $where = [ ['pcs.student_id', '=', $studentId], ['pcs.deleted_at', '=', 0], ['pcs.course_type', '=', 3] // 3-预约课程 ]; // 状态筛选 if (!empty($params['status'])) { $where[] = ['pcs.status', '=', $params['status']]; } // 日期范围筛选 if (!empty($params['start_date']) && !empty($params['end_date'])) { $where[] = ['pcs.course_date', 'between', [$params['start_date'], $params['end_date']]]; } // 查询预约列表 $bookingList = Db::table('school_person_course_schedule pcs') ->leftJoin('school_course_schedule cs', 'pcs.schedule_id = cs.id') ->leftJoin('school_course c', 'cs.course_id = c.id') ->leftJoin('school_personnel p', 'cs.coach_id = p.id') ->leftJoin('school_venue v', 'cs.venue_id = v.id') ->where($where) ->field(' pcs.id, pcs.course_date as booking_date, pcs.time_slot, pcs.status, pcs.cancel_reason, pcs.remark, COALESCE(cs.start_time, CASE WHEN pcs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(pcs.time_slot, "-", 1) ELSE "09:00" END ) as start_time, COALESCE(cs.end_time, CASE WHEN pcs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(pcs.time_slot, "-", -1) ELSE "10:00" END ) as end_time, c.course_name as course_type, p.name as coach_name, v.venue_name, pcs.created_at ') ->order('pcs.course_date desc, pcs.created_at desc') ->select() ->toArray(); // 处理数据格式 foreach ($bookingList as &$booking) { $booking['status_text'] = $this->getBookingStatusText($booking['status']); } return [ 'list' => $bookingList, 'total' => count($bookingList) ]; } /** * 取消课程预约 * @param array $data * @return bool */ public function cancelBooking($data) { $bookingId = $data['booking_id']; $cancelReason = $data['cancel_reason'] ?? ''; // 查询预约记录 $booking = Db::table('school_person_course_schedule') ->where('id', $bookingId) ->where('deleted_at', 0) ->find(); if (!$booking) { throw new CommonException('预约记录不存在'); } // 验证学员权限 $this->checkStudentPermission($booking['student_id']); // 检查预约状态 if ($booking['status'] != 0) { // 0-待上课 throw new CommonException('当前预约状态不允许取消'); } // 检查取消时间限制(上课前6小时) $courseDateTime = $booking['course_date'] . ' ' . ($booking['start_time'] ?: '09:00'); $courseTimestamp = strtotime($courseDateTime); $currentTimestamp = time(); if ($courseTimestamp - $currentTimestamp < 6 * 3600) { throw new CommonException('上课前6小时内不允许取消预约'); } // 更新预约状态为取消 $result = Db::table('school_person_course_schedule') ->where('id', $bookingId) ->update([ 'status' => 3, // 3-取消 'cancel_reason' => $cancelReason, 'updated_at' => date('Y-m-d H:i:s') ]); if ($result === false) { throw new CommonException('取消预约失败'); } // TODO: 发送取消预约消息通知 return true; } /** * 检查预约冲突 * @param array $data * @return array */ public function checkBookingConflict($data) { $studentId = $data['student_id']; $bookingDate = $data['booking_date']; $timeSlot = $data['time_slot']; // 查询同一时间段的预约 $conflictBooking = Db::table('school_person_course_schedule') ->where('student_id', $studentId) ->where('course_date', $bookingDate) ->where('time_slot', $timeSlot) ->where('deleted_at', 0) ->where('status', '!=', 3) // 非取消状态 ->find(); return [ 'has_conflict' => !empty($conflictBooking), 'conflict_booking' => $conflictBooking ]; } /** * 检查学员权限(确保只能操作自己的预约) * @param int $studentId * @return bool */ private function checkStudentPermission($studentId) { $customerId = $this->getUserId(); // 检查学员是否属于当前用户 $student = (new Student()) ->where('id', $studentId) ->where('user_id', $customerId) ->where('deleted_at', 0) ->find(); if (!$student) { throw new CommonException('无权限访问该学员信息'); } return true; } /** * 获取预约状态文本 * @param int $status * @return string */ private function getBookingStatusText($status) { $statusMap = [ 0 => '待上课', 1 => '已完成', 2 => '请假', 3 => '已取消' ]; return $statusMap[$status] ?? '未知状态'; } /** * 获取当前登录用户ID * @return int */ private function getUserId() { // 从request中获取memberId(由ApiCheckToken中间件设置) $memberId = request()->memberId(); if ($memberId) { return $memberId; } // 如果没有中间件设置,尝试解析token $token = request()->header('token'); if ($token) { try { $loginService = new \app\service\api\login\LoginService(); $tokenInfo = $loginService->parseToken($token); if (!empty($tokenInfo) && isset($tokenInfo['member_id'])) { return $tokenInfo['member_id']; } } catch (\Exception $e) { // token解析失败,抛出异常 throw new CommonException('用户未登录或token无效'); } } // 如果都没有,抛出异常 throw new CommonException('用户未登录'); } }