智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

455 lines
15 KiB

<?php
// +----------------------------------------------------------------------
// | 课程预约服务类
// +----------------------------------------------------------------------
namespace app\service\api\student;
use app\model\student\Student;
use app\model\customer_resources\CustomerResources;
use think\facade\Db;
use core\base\BaseService;
use core\exception\CommonException;
/**
* 课程预约服务类
*/
class CourseBookingService extends BaseService
{
/**
* 获取可预约课程列表
* @param array $params
* @return array
*/
public function getAvailableCourses($params)
{
$studentId = $params['student_id'];
// 验证学员权限
$this->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('用户未登录');
}
}