|
|
@ -92,6 +92,9 @@ class CourseBookingService extends BaseService |
|
|
->select() |
|
|
->select() |
|
|
->toArray(); |
|
|
->toArray(); |
|
|
|
|
|
|
|
|
|
|
|
// 获取学员预约资格信息 |
|
|
|
|
|
$studentQualification = $this->checkStudentQualification($studentId); |
|
|
|
|
|
|
|
|
// 处理每个课程的预约状态 |
|
|
// 处理每个课程的预约状态 |
|
|
foreach ($availableCourses as &$course) { |
|
|
foreach ($availableCourses as &$course) { |
|
|
// 计算已预约人数 |
|
|
// 计算已预约人数 |
|
|
@ -109,7 +112,7 @@ class CourseBookingService extends BaseService |
|
|
->where('student_id', $studentId) |
|
|
->where('student_id', $studentId) |
|
|
->where('schedule_id', $course['id']) |
|
|
->where('schedule_id', $course['id']) |
|
|
->where('deleted_at', 0) |
|
|
->where('deleted_at', 0) |
|
|
->where('status', '!=', 3) // 3-取消 |
|
|
->where('status', '<>', 3) // 3-取消 |
|
|
->find(); |
|
|
->find(); |
|
|
|
|
|
|
|
|
// 确定课程状态 |
|
|
// 确定课程状态 |
|
|
@ -117,6 +120,9 @@ class CourseBookingService extends BaseService |
|
|
$course['booking_status'] = 'booked'; |
|
|
$course['booking_status'] = 'booked'; |
|
|
} elseif ($bookedCount >= $course['max_students']) { |
|
|
} elseif ($bookedCount >= $course['max_students']) { |
|
|
$course['booking_status'] = 'full'; |
|
|
$course['booking_status'] = 'full'; |
|
|
|
|
|
} elseif (!$studentQualification['can_book']) { |
|
|
|
|
|
$course['booking_status'] = 'no_permission'; |
|
|
|
|
|
$course['no_permission_reason'] = $studentQualification['reason']; |
|
|
} else { |
|
|
} else { |
|
|
$course['booking_status'] = 'available'; |
|
|
$course['booking_status'] = 'available'; |
|
|
} |
|
|
} |
|
|
@ -144,6 +150,12 @@ class CourseBookingService extends BaseService |
|
|
// 验证学员权限 |
|
|
// 验证学员权限 |
|
|
$this->checkStudentPermission($studentId); |
|
|
$this->checkStudentPermission($studentId); |
|
|
|
|
|
|
|
|
|
|
|
// 检查学员预约资格 |
|
|
|
|
|
$studentQualification = $this->checkStudentQualification($studentId); |
|
|
|
|
|
if (!$studentQualification['can_book']) { |
|
|
|
|
|
throw new CommonException($studentQualification['reason']); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 检查课程安排是否存在 |
|
|
// 检查课程安排是否存在 |
|
|
$courseSchedule = Db::table('school_course_schedule') |
|
|
$courseSchedule = Db::table('school_course_schedule') |
|
|
->where('id', $scheduleId) |
|
|
->where('id', $scheduleId) |
|
|
@ -169,7 +181,7 @@ class CourseBookingService extends BaseService |
|
|
$bookedCount = Db::table('school_person_course_schedule') |
|
|
$bookedCount = Db::table('school_person_course_schedule') |
|
|
->where('schedule_id', $scheduleId) |
|
|
->where('schedule_id', $scheduleId) |
|
|
->where('deleted_at', 0) |
|
|
->where('deleted_at', 0) |
|
|
->where('status', '!=', 3) // 非取消状态 |
|
|
->where('status', '<>', 3) // 非取消状态 |
|
|
->count(); |
|
|
->count(); |
|
|
|
|
|
|
|
|
$maxStudents = $courseSchedule['max_students'] ?: 10; |
|
|
$maxStudents = $courseSchedule['max_students'] ?: 10; |
|
|
@ -182,13 +194,19 @@ class CourseBookingService extends BaseService |
|
|
->where('student_id', $studentId) |
|
|
->where('student_id', $studentId) |
|
|
->where('schedule_id', $scheduleId) |
|
|
->where('schedule_id', $scheduleId) |
|
|
->where('deleted_at', 0) |
|
|
->where('deleted_at', 0) |
|
|
->where('status', '!=', 3) |
|
|
->where('status', '<>', 3) |
|
|
->find(); |
|
|
->find(); |
|
|
|
|
|
|
|
|
if ($existingBooking) { |
|
|
if ($existingBooking) { |
|
|
throw new CommonException('您已预约过此课程'); |
|
|
throw new CommonException('您已预约过此课程'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 判断预约类型(正式课程或试听课) |
|
|
|
|
|
$bookingType = $studentQualification['has_valid_course'] ? 1 : 4; // 1-正式课程, 4-试听课 |
|
|
|
|
|
|
|
|
|
|
|
// 开启事务 |
|
|
|
|
|
Db::startTrans(); |
|
|
|
|
|
try { |
|
|
// 创建预约记录 |
|
|
// 创建预约记录 |
|
|
$bookingData = [ |
|
|
$bookingData = [ |
|
|
'student_id' => $studentId, |
|
|
'student_id' => $studentId, |
|
|
@ -196,30 +214,44 @@ class CourseBookingService extends BaseService |
|
|
'course_date' => $data['course_date'], |
|
|
'course_date' => $data['course_date'], |
|
|
'time_slot' => $data['time_slot'], |
|
|
'time_slot' => $data['time_slot'], |
|
|
'person_type' => 'student', |
|
|
'person_type' => 'student', |
|
|
'course_type' => 3, // 3-预约课程 |
|
|
'course_type' => $bookingType, |
|
|
'status' => 0, // 0-待上课 |
|
|
'status' => 0, // 0-待上课 |
|
|
'remark' => $data['note'] ?? '', |
|
|
'remark' => $data['remark'] ?? '', |
|
|
'created_at' => date('Y-m-d H:i:s'), |
|
|
'created_at' => date('Y-m-d H:i:s'), |
|
|
'updated_at' => date('Y-m-d H:i:s'), |
|
|
'updated_at' => date('Y-m-d H:i:s'), |
|
|
'deleted_at' => 0 |
|
|
'deleted_at' => 0 |
|
|
]; |
|
|
]; |
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
$bookingId = Db::table('school_person_course_schedule')->insertGetId($bookingData); |
|
|
$bookingId = Db::table('school_person_course_schedule')->insertGetId($bookingData); |
|
|
|
|
|
|
|
|
if (!$bookingId) { |
|
|
if (!$bookingId) { |
|
|
throw new CommonException('预约创建失败'); |
|
|
throw new CommonException('预约创建失败'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 预约时不扣减课时,等实际上课时再扣减 |
|
|
|
|
|
// 只记录预约信息,包含关联的课程ID(用于后续课时扣减) |
|
|
|
|
|
if ($bookingType == 1 && !empty($studentQualification['best_course'])) { |
|
|
|
|
|
// 更新预约记录,关联学员课程ID |
|
|
|
|
|
Db::table('school_person_course_schedule') |
|
|
|
|
|
->where('id', $bookingId) |
|
|
|
|
|
->update(['student_course_id' => $studentQualification['best_course']['id']]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 提交事务 |
|
|
|
|
|
Db::commit(); |
|
|
|
|
|
|
|
|
// TODO: 发送预约成功消息通知 |
|
|
// TODO: 发送预约成功消息通知 |
|
|
|
|
|
|
|
|
return [ |
|
|
return [ |
|
|
'booking_id' => $bookingId, |
|
|
'booking_id' => $bookingId, |
|
|
'status' => 'success', |
|
|
'status' => 'success', |
|
|
'message' => '预约创建成功' |
|
|
'message' => '预约创建成功', |
|
|
|
|
|
'booking_type' => $bookingType == 1 ? 'formal' : 'trial' |
|
|
]; |
|
|
]; |
|
|
|
|
|
|
|
|
} catch (\Exception $e) { |
|
|
} catch (\Exception $e) { |
|
|
|
|
|
// 回滚事务 |
|
|
|
|
|
Db::rollback(); |
|
|
throw new CommonException('预约创建失败:' . $e->getMessage()); |
|
|
throw new CommonException('预约创建失败:' . $e->getMessage()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -239,8 +271,8 @@ class CourseBookingService extends BaseService |
|
|
// 构建查询条件 |
|
|
// 构建查询条件 |
|
|
$where = [ |
|
|
$where = [ |
|
|
['pcs.student_id', '=', $studentId], |
|
|
['pcs.student_id', '=', $studentId], |
|
|
['pcs.deleted_at', '=', 0], |
|
|
['pcs.deleted_at', '=', 0] |
|
|
['pcs.course_type', '=', 3] // 3-预约课程 |
|
|
// 移除course_type限制,因为我们改成了1(正式课程)和4(试听课) |
|
|
]; |
|
|
]; |
|
|
|
|
|
|
|
|
// 状态筛选 |
|
|
// 状态筛选 |
|
|
@ -330,7 +362,13 @@ class CourseBookingService extends BaseService |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 检查取消时间限制(上课前6小时) |
|
|
// 检查取消时间限制(上课前6小时) |
|
|
$courseDateTime = $booking['course_date'] . ' ' . ($booking['start_time'] ?: '09:00'); |
|
|
// 从time_slot中提取开始时间 |
|
|
|
|
|
$startTime = '09:00'; // 默认开始时间 |
|
|
|
|
|
if (!empty($booking['time_slot']) && preg_match('/^(\d{2}:\d{2})-\d{2}:\d{2}$/', $booking['time_slot'], $matches)) { |
|
|
|
|
|
$startTime = $matches[1]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$courseDateTime = $booking['course_date'] . ' ' . $startTime; |
|
|
$courseTimestamp = strtotime($courseDateTime); |
|
|
$courseTimestamp = strtotime($courseDateTime); |
|
|
$currentTimestamp = time(); |
|
|
$currentTimestamp = time(); |
|
|
|
|
|
|
|
|
@ -338,6 +376,9 @@ class CourseBookingService extends BaseService |
|
|
throw new CommonException('上课前6小时内不允许取消预约'); |
|
|
throw new CommonException('上课前6小时内不允许取消预约'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 开启事务 |
|
|
|
|
|
Db::startTrans(); |
|
|
|
|
|
try { |
|
|
// 更新预约状态为取消 |
|
|
// 更新预约状态为取消 |
|
|
$result = Db::table('school_person_course_schedule') |
|
|
$result = Db::table('school_person_course_schedule') |
|
|
->where('id', $bookingId) |
|
|
->where('id', $bookingId) |
|
|
@ -351,9 +392,22 @@ class CourseBookingService extends BaseService |
|
|
throw new CommonException('取消预约失败'); |
|
|
throw new CommonException('取消预约失败'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 预约时不扣减课时,取消预约时也不需要恢复课时 |
|
|
|
|
|
// 课时只在实际签到上课时扣减 |
|
|
|
|
|
// 这里只需要更新预约状态为取消即可 |
|
|
|
|
|
|
|
|
|
|
|
// 提交事务 |
|
|
|
|
|
Db::commit(); |
|
|
|
|
|
|
|
|
// TODO: 发送取消预约消息通知 |
|
|
// TODO: 发送取消预约消息通知 |
|
|
|
|
|
|
|
|
return true; |
|
|
return true; |
|
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) { |
|
|
|
|
|
// 回滚事务 |
|
|
|
|
|
Db::rollback(); |
|
|
|
|
|
throw new CommonException('取消预约失败:' . $e->getMessage()); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -373,7 +427,7 @@ class CourseBookingService extends BaseService |
|
|
->where('course_date', $bookingDate) |
|
|
->where('course_date', $bookingDate) |
|
|
->where('time_slot', $timeSlot) |
|
|
->where('time_slot', $timeSlot) |
|
|
->where('deleted_at', 0) |
|
|
->where('deleted_at', 0) |
|
|
->where('status', '!=', 3) // 非取消状态 |
|
|
->where('status', '<>', 3) // 非取消状态 |
|
|
->find(); |
|
|
->find(); |
|
|
|
|
|
|
|
|
return [ |
|
|
return [ |
|
|
@ -452,4 +506,193 @@ class CourseBookingService extends BaseService |
|
|
// 如果都没有,抛出异常 |
|
|
// 如果都没有,抛出异常 |
|
|
throw new CommonException('用户未登录'); |
|
|
throw new CommonException('用户未登录'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 检查学员预约资格 |
|
|
|
|
|
* @param int $studentId |
|
|
|
|
|
* @return array |
|
|
|
|
|
*/ |
|
|
|
|
|
public function checkStudentQualification($studentId) |
|
|
|
|
|
{ |
|
|
|
|
|
// 查询学员有效的正式课程 |
|
|
|
|
|
$validCourses = Db::table('school_student_courses') |
|
|
|
|
|
->where('student_id', $studentId) |
|
|
|
|
|
->where('status', 1) // 有效状态 |
|
|
|
|
|
->where('start_date', '<=', date('Y-m-d')) |
|
|
|
|
|
->where('end_date', '>=', date('Y-m-d')) |
|
|
|
|
|
->select() |
|
|
|
|
|
->toArray(); |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有剩余课时的课程 |
|
|
|
|
|
$hasValidCourse = false; |
|
|
|
|
|
$bestCourse = null; |
|
|
|
|
|
$totalRemainingHours = 0; |
|
|
|
|
|
|
|
|
|
|
|
foreach ($validCourses as $course) { |
|
|
|
|
|
$totalHours = $course['total_hours'] + $course['gift_hours']; |
|
|
|
|
|
$usedHours = $course['use_total_hours'] + $course['use_gift_hours']; |
|
|
|
|
|
$remainingHours = $totalHours - $usedHours; |
|
|
|
|
|
|
|
|
|
|
|
if ($remainingHours > 0) { |
|
|
|
|
|
$hasValidCourse = true; |
|
|
|
|
|
$totalRemainingHours += $remainingHours; |
|
|
|
|
|
|
|
|
|
|
|
// 选择剩余课时最多的课程 |
|
|
|
|
|
if (!$bestCourse || $remainingHours > ($bestCourse['remaining_hours'] ?? 0)) { |
|
|
|
|
|
$bestCourse = $course; |
|
|
|
|
|
$bestCourse['remaining_hours'] = $remainingHours; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果有有效课程,直接允许预约 |
|
|
|
|
|
if ($hasValidCourse) { |
|
|
|
|
|
return [ |
|
|
|
|
|
'can_book' => true, |
|
|
|
|
|
'has_valid_course' => true, |
|
|
|
|
|
'total_remaining_hours' => $totalRemainingHours, |
|
|
|
|
|
'best_course' => $bestCourse, |
|
|
|
|
|
'reason' => '' |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 没有有效课程,检查试听课次数 |
|
|
|
|
|
$student = Db::table('school_student') |
|
|
|
|
|
->where('id', $studentId) |
|
|
|
|
|
->where('deleted_at', 0) |
|
|
|
|
|
->find(); |
|
|
|
|
|
|
|
|
|
|
|
if (!$student) { |
|
|
|
|
|
return [ |
|
|
|
|
|
'can_book' => false, |
|
|
|
|
|
'has_valid_course' => false, |
|
|
|
|
|
'trial_class_count' => 0, |
|
|
|
|
|
'reason' => '学员信息不存在' |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$trialClassCount = $student['trial_class_count'] ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
if ($trialClassCount > 0) { |
|
|
|
|
|
return [ |
|
|
|
|
|
'can_book' => true, |
|
|
|
|
|
'has_valid_course' => false, |
|
|
|
|
|
'trial_class_count' => $trialClassCount, |
|
|
|
|
|
'reason' => '' |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 既没有有效课程,也没有试听课次数 |
|
|
|
|
|
return [ |
|
|
|
|
|
'can_book' => false, |
|
|
|
|
|
'has_valid_course' => false, |
|
|
|
|
|
'trial_class_count' => 0, |
|
|
|
|
|
'reason' => '课时不足且无试听课次数,请联系客服购买课程' |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 扣减正式课课时 |
|
|
|
|
|
* @param int $studentId |
|
|
|
|
|
* @param array $courseInfo |
|
|
|
|
|
* @return bool |
|
|
|
|
|
*/ |
|
|
|
|
|
private function deductCourseHours($studentId, $courseInfo) |
|
|
|
|
|
{ |
|
|
|
|
|
$courseId = $courseInfo['id']; |
|
|
|
|
|
|
|
|
|
|
|
// 优先扣减赠课课时,再扣减购买课时 |
|
|
|
|
|
if ($courseInfo['gift_hours'] > $courseInfo['use_gift_hours']) { |
|
|
|
|
|
// 扣减赠课课时 |
|
|
|
|
|
$result = Db::table('school_student_courses') |
|
|
|
|
|
->where('id', $courseId) |
|
|
|
|
|
->inc('use_gift_hours', 1) |
|
|
|
|
|
->update(); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 扣减购买课时 |
|
|
|
|
|
$result = Db::table('school_student_courses') |
|
|
|
|
|
->where('id', $courseId) |
|
|
|
|
|
->inc('use_total_hours', 1) |
|
|
|
|
|
->update(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ($result === false) { |
|
|
|
|
|
throw new CommonException('扣减课时失败'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 扣减试听课次数 |
|
|
|
|
|
* @param int $studentId |
|
|
|
|
|
* @return bool |
|
|
|
|
|
*/ |
|
|
|
|
|
private function deductTrialClassCount($studentId) |
|
|
|
|
|
{ |
|
|
|
|
|
$result = Db::table('school_student') |
|
|
|
|
|
->where('id', $studentId) |
|
|
|
|
|
->where('trial_class_count', '>', 0) |
|
|
|
|
|
->dec('trial_class_count', 1) |
|
|
|
|
|
->update(); |
|
|
|
|
|
|
|
|
|
|
|
if ($result === false) { |
|
|
|
|
|
throw new CommonException('扣减试听课次数失败'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 恢复正式课课时 |
|
|
|
|
|
* @param int $studentId |
|
|
|
|
|
* @param int $studentCourseId |
|
|
|
|
|
* @return bool |
|
|
|
|
|
*/ |
|
|
|
|
|
private function restoreCourseHours($studentId, $studentCourseId) |
|
|
|
|
|
{ |
|
|
|
|
|
if (!$studentCourseId) { |
|
|
|
|
|
return false; // 如果没有关联的学员课程ID,无法恢复 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$courseInfo = Db::table('school_student_courses') |
|
|
|
|
|
->where('id', $studentCourseId) |
|
|
|
|
|
->find(); |
|
|
|
|
|
|
|
|
|
|
|
if (!$courseInfo) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 优先恢复赠课课时,再恢复购买课时 |
|
|
|
|
|
if ($courseInfo['use_gift_hours'] > 0) { |
|
|
|
|
|
// 恢复赠课课时 |
|
|
|
|
|
$result = Db::table('school_student_courses') |
|
|
|
|
|
->where('id', $studentCourseId) |
|
|
|
|
|
->dec('use_gift_hours', 1) |
|
|
|
|
|
->update(); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 恢复购买课时 |
|
|
|
|
|
$result = Db::table('school_student_courses') |
|
|
|
|
|
->where('id', $studentCourseId) |
|
|
|
|
|
->dec('use_total_hours', 1) |
|
|
|
|
|
->update(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return $result !== false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 恢复试听课次数 |
|
|
|
|
|
* @param int $studentId |
|
|
|
|
|
* @return bool |
|
|
|
|
|
*/ |
|
|
|
|
|
private function restoreTrialClassCount($studentId) |
|
|
|
|
|
{ |
|
|
|
|
|
$result = Db::table('school_student') |
|
|
|
|
|
->where('id', $studentId) |
|
|
|
|
|
->inc('trial_class_count', 1) |
|
|
|
|
|
->update(); |
|
|
|
|
|
|
|
|
|
|
|
return $result !== false; |
|
|
|
|
|
} |
|
|
} |
|
|
} |