智慧教务系统
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.
 
 
 
 
 
 

3213 lines
106 KiB

<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\service\api\apiService;
use app\model\student\Student;
use core\base\BaseApiService;
use think\facade\Db;
/**
* 课程安排服务类
*/
class CourseScheduleService extends BaseApiService
{
// 定义表前缀,可以更方便地引用数据表
protected $prefix = 'school_';
public function __construct()
{
parent::__construct();
$this->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 getScheduleDetail(array $data)
{
try {
$scheduleId = $data['schedule_id'];
if (empty($scheduleId)) {
return [
'code' => 0,
'msg' => '课程安排ID不能为空',
'data' => null
];
}
// 获取课程基本信息
$scheduleInfo = 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 . 'class cla', 'cs.class_id = cla.id')
->where('cs.id', $scheduleId)
->where('cs.deleted_at', 0)
->field([
'cs.*',
'c.course_name',
'v.venue_name',
'cap.campus_name',
'coach.name as coach_name',
'cla.class_name'
])
->find();
if (!$scheduleInfo) {
return [
'code' => 0,
'msg' => '课程安排不存在',
'data' => null
];
}
// 获取学员列表
$students = Db::name('person_course_schedule')
->alias('pcs')
->leftJoin($this->prefix . 'customer_resources cr', 'pcs.resources_id = cr.id')
->leftJoin($this->prefix . 'student s', 'pcs.student_id = s.id')
->leftJoin($this->prefix . 'student_courses sc', 'pcs.student_id = sc.student_id AND sc.course_id = pcs.schedule_id')
->where('pcs.schedule_id', $scheduleId)
->where('pcs.deleted_at', 0)
->field([
'pcs.id',
'pcs.person_id',
'pcs.student_id',
'pcs.resources_id',
'pcs.person_type',
'pcs.schedule_type',
'pcs.status',
'cr.name as resource_name',
'cr.phone_number',
'cr.age',
's.name as student_name',
's.contact_phone as student_phone',
'sc.end_date',
'sc.use_total_hours as used_session_count',
'sc.total_hours as total_session_count'
])
->group('pcs.id') // 防止重复数据
->select()
->toArray();
// 分类处理学员数据
$formalStudents = [];
$waitingStudents = [];
foreach ($students as $student) {
$studentData = $this->formatStudentDataForDetail($student);
// 如果schedule_type为空,默认为等待位(2)
$scheduleType = $student['schedule_type'] ?? 2;
if ($scheduleType == 1) {
$formalStudents[] = $studentData;
} else {
$waitingStudents[] = $studentData;
}
}
// 计算空位
$maxFormalSeats = $scheduleInfo['max_students'] ?? 10;
$maxWaitingSeats = 5; // 等待位默认5个
$formalEmptySeats = max(0, $maxFormalSeats - count($formalStudents));
$waitingEmptySeats = max(0, $maxWaitingSeats - count($waitingStudents));
return [
'code' => 1,
'msg' => '获取成功',
'data' => [
'schedule_info' => $scheduleInfo,
'formal_students' => $formalStudents,
'waiting_students' => $waitingStudents,
'formal_empty_seats' => range(1, $formalEmptySeats),
'waiting_empty_seats' => range(1, $waitingEmptySeats)
]
];
} catch (\Exception $e) {
return [
'code' => 0,
'msg' => '获取课程安排详情失败:' . $e->getMessage(),
'data' => null
];
}
}
/**
* 格式化学员数据用于详情显示
* @param array $student 学员数据
* @return array 格式化后的学员数据
*/
private function formatStudentDataForDetail($student)
{
// 获取课程进度
$courseProgress = [
'used' => $student['used_session_count'] ?? 0,
'total' => $student['total_session_count'] ?? 0,
'percentage' => 0
];
if ($courseProgress['total'] > 0) {
$courseProgress['percentage'] = round(($courseProgress['used'] / $courseProgress['total']) * 100, 1);
}
// 转换数据库枚举值为前端期望的值
$personType = $student['person_type'];
if ($personType === 'customer_resource') {
$personType = 'resource';
}
return [
'id' => $student['id'],
'person_id' => $student['person_id'],
'student_id' => $student['student_id'],
'resources_id' => $student['resources_id'],
'name' => $student['resource_name'] ?: $student['student_name'],
'phone' => $student['phone_number'] ?: $student['student_phone'],
'age' => $student['age'] ?? '',
'person_type' => $personType,
'schedule_type' => $student['schedule_type'] ?? 2, // 默认为等待位
'status' => $student['status'],
'courseStatus' => $this->getStudentStatusText($student['status']),
'course_progress' => $courseProgress,
'renewal_status' => false, // 可以根据实际逻辑判断
'student_course_info' => [
'end_date' => $student['end_date'] ?? ''
]
];
}
/**
* 智能搜索学员
* @param array $data 搜索参数
* @return array 搜索结果
*/
public function searchStudents(array $data)
{
try {
$phoneNumber = $data['phone_number'] ?? '';
$name = $data['name'] ?? '';
if (empty($phoneNumber) && empty($name)) {
return [
'code' => 1,
'msg' => '请输入搜索条件',
'data' => []
];
}
$where = [];
// 构建搜索条件
if (!empty($phoneNumber)) {
$where[] = ['cr.phone_number', 'like', '%' . $phoneNumber . '%'];
}
if (!empty($name)) {
$where[] = ['cr.name', 'like', '%' . $name . '%'];
}
// 搜索客户资源表
$results = Db::name('customer_resources')
->alias('cr')
->leftJoin($this->prefix . 'student s', 'cr.member_id = s.user_id')
->leftJoin($this->prefix . 'student_courses sc', 's.id = sc.student_id')
->where($where)
->where('cr.deleted_at', 0)
->field([
'cr.id as resource_id',
'cr.name',
'cr.phone_number',
'cr.age',
'cr.member_id',
's.id as student_id',
'sc.id as student_course_id',
'sc.status as course_status',
'sc.total_hours as total_session_count',
'sc.use_total_hours as used_session_count'
])
->limit(20)
->select()
->toArray();
// 处理结果数据
$searchResults = [];
foreach ($results as $result) {
// 判断学员类型:如果有正式的学员课程记录,则为正式学员;否则为客户资源
$person_type = !empty($result['student_course_id']) ? 'student' : 'resource';
$searchResults[] = [
'id' => $result['resource_id'],
'name' => $result['name'],
'phone_number' => $result['phone_number'],
'phone' => $result['phone_number'], // 添加phone字段别名
'age' => $result['age'],
'member_id' => $result['member_id'],
'student_id' => $result['student_id'] ?? 0,
'resource_id' => $result['resource_id'],
'resources_id' => $result['resource_id'], // 添加resources_id字段别名
'person_type' => $person_type, // 添加person_type字段
'is_formal_student' => !empty($result['student_course_id']),
'course_info' => [
'course_status' => $result['course_status'] ?? '',
'total_sessions' => $result['total_session_count'] ?? 0,
'used_sessions' => $result['used_session_count'] ?? 0
]
];
}
return [
'code' => 1,
'msg' => '搜索成功',
'data' => $searchResults
];
} catch (\Exception $e) {
return [
'code' => 0,
'msg' => '搜索失败:' . $e->getMessage(),
'data' => []
];
}
}
/**
* 删除学员/请假处理
* @param array $data 删除参数
* @return array 处理结果
*/
public function removeStudent(array $data)
{
try {
$scheduleId = $data['schedule_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
$personType = $data['person_type'] ?? '';
$reason = $data['reason'] ?? '删除学员';
if (empty($scheduleId) || empty($personId)) {
return [
'code' => 0,
'msg' => '参数不完整'
];
}
// 开启事务
Db::startTrans();
// 查找学员记录
$enrollment = Db::name('person_course_schedule')
->where('schedule_id', $scheduleId)
->where('person_id', $personId)
->where('deleted_at', 0)
->find();
if (!$enrollment) {
Db::rollback();
return [
'code' => 0,
'msg' => '找不到学员记录'
];
}
// 判断是请假还是删除
if (strpos($reason, '请假') !== false) {
// 请假处理:更新状态为请假
$result = Db::name('person_course_schedule')
->where('id', $enrollment['id'])
->update([
'status' => 2, // 请假状态
'updated_at' => date('Y-m-d H:i:s'),
'remark' => $reason
]);
} else {
// 删除处理:软删除记录
$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' => $reason
]);
}
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 addStudentToSchedule(array $data)
{
try {
$scheduleId = $data['schedule_id'] ?? 0;
$personType = $data['person_type'] ?? '';
$scheduleType = $data['schedule_type'] ?? 2; // 默认等待位
if (empty($scheduleId) || empty($personType)) {
return [
'code' => 0,
'msg' => '课程安排ID和人员类型不能为空'
];
}
// 转换前端传递的person_type为数据库枚举值
if ($personType === 'resource') {
$personType = 'customer_resource';
}
// 开启事务
Db::startTrans();
// 查询课程安排信息
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->where('deleted_at', 0)
->find();
if (!$schedule) {
Db::rollback();
return [
'code' => 0,
'msg' => '课程安排不存在或已被删除'
];
}
// 准备插入数据
$insertData = [
'schedule_id' => $scheduleId,
'person_type' => $personType,
'schedule_type' => $scheduleType,
'course_type' => $scheduleType == 1 ? 1 : 3, // 正式位为正常课程,等待位为等待位课程
'status' => 0, // 待上课
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'deleted_at' => 0
];
// 根据人员类型设置对应的ID
if ($personType === 'student') {
$studentId = $data['student_id'] ?? 0;
if (empty($studentId)) {
Db::rollback();
return [
'code' => 0,
'msg' => '学员ID不能为空'
];
}
$insertData['student_id'] = $studentId;
$insertData['person_id'] = $studentId;
} else {
$resourcesId = $data['resources_id'] ?? 0;
if (empty($resourcesId)) {
Db::rollback();
return [
'code' => 0,
'msg' => '客户资源ID不能为空'
];
}
$insertData['resources_id'] = $resourcesId;
$insertData['person_id'] = $resourcesId;
}
// 检查是否已经添加过
$whereCheck = [
'schedule_id' => $scheduleId,
'deleted_at' => 0
];
if ($personType === 'student') {
$whereCheck['student_id'] = $insertData['student_id'];
} else {
$whereCheck['resources_id'] = $insertData['resources_id'];
}
$exists = Db::name('person_course_schedule')
->where($whereCheck)
->find();
if ($exists) {
Db::rollback();
return [
'code' => 0,
'msg' => '该学员已经在此课程安排中'
];
}
// 检查容量限制
if ($scheduleType == 1) {
// 检查正式位容量
$formalCount = Db::name('person_course_schedule')
->where('schedule_id', $scheduleId)
->where('schedule_type', 1)
->where('deleted_at', 0)
->count();
$maxStudents = intval($schedule['max_students'] ?? 10);
if ($formalCount >= $maxStudents) {
Db::rollback();
return [
'code' => 0,
'msg' => '正式位已满,无法添加'
];
}
} else {
// 检查等待位容量(默认最多5个)
$waitingCount = Db::name('person_course_schedule')
->where('schedule_id', $scheduleId)
->where('schedule_type', 2)
->where('deleted_at', 0)
->count();
if ($waitingCount >= 5) {
Db::rollback();
return [
'code' => 0,
'msg' => '等待位已满,无法添加'
];
}
}
// 插入学员记录
$result = Db::name('person_course_schedule')->insert($insertData);
if (!$result) {
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 upgradeStudent(array $data)
{
try {
$scheduleId = $data['schedule_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
$fromType = $data['from_type'] ?? 2;
$toType = $data['to_type'] ?? 1;
if (empty($scheduleId) || empty($personId)) {
return [
'code' => 0,
'msg' => '参数不完整'
];
}
// 验证升级方向
if ($fromType != 2 || $toType != 1) {
return [
'code' => 0,
'msg' => '只能从等待位升级到正式位'
];
}
// 开启事务
Db::startTrans();
// 查找等待位学员记录
$enrollment = Db::name('person_course_schedule')
->where('schedule_id', $scheduleId)
->where('person_id', $personId)
->where('schedule_type', 2)
->where('deleted_at', 0)
->find();
if (!$enrollment) {
Db::rollback();
return [
'code' => 0,
'msg' => '找不到等待位学员记录'
];
}
// 检查正式位容量
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->find();
$formalCount = Db::name('person_course_schedule')
->where('schedule_id', $scheduleId)
->where('schedule_type', 1)
->where('deleted_at', 0)
->count();
$maxStudents = intval($schedule['max_students'] ?? 10);
if ($formalCount >= $maxStudents) {
Db::rollback();
return [
'code' => 0,
'msg' => '正式位已满,无法升级'
];
}
// 更新学员记录
$result = Db::name('person_course_schedule')
->where('id', $enrollment['id'])
->update([
'schedule_type' => 1,
'course_type' => 1, // 等待位课程改为正式课
'updated_at' => date('Y-m-d H:i:s'),
'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()
];
}
}
/**
* 更新学员签到状态
* @param array $data 更新参数
* @return array 更新结果
*/
public function updateStudentStatus(array $data)
{
try {
$scheduleId = $data['schedule_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
$status = $data['status'] ?? 0;
$reason = $data['reason'] ?? '';
$resources_id = $data['resources_id'] ?? 0;
if (empty($scheduleId) || empty($personId)) {
return [
'code' => 0,
'msg' => '参数不完整'
];
}
// 验证状态值
if (!in_array($status, [0, 1, 2])) {
return [
'code' => 0,
'msg' => '无效的状态值'
];
}
// 开启事务
Db::startTrans();
// 使用内部处理方法
$result = $this->processSingleStudentSignIn($scheduleId, $personId, $resources_id, $status, $reason);
if (!$result['success']) {
Db::rollback();
return [
'code' => 0,
'msg' => $result['error']
];
}
// 提交事务
Db::commit();
return [
'code' => 1,
'msg' => '更新成功'
];
} catch (\Exception $e) {
Db::rollback();
return [
'code' => 0,
'msg' => '更新失败:' . $e->getMessage()
];
}
}
/**
* 恢复学员(取消请假)
* @param array $data 恢复参数
* @return array 恢复结果
*/
public function restoreStudent(array $data)
{
try {
$scheduleId = $data['schedule_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
if (empty($scheduleId) || empty($personId)) {
return [
'code' => 0,
'msg' => '参数不完整'
];
}
// 开启事务
Db::startTrans();
// 查找学员记录
$enrollment = Db::name('person_course_schedule')
->where('schedule_id', $scheduleId)
->where('person_id', $personId)
->where('deleted_at', 0)
->find();
if (!$enrollment) {
Db::rollback();
return [
'code' => 0,
'msg' => '找不到学员记录'
];
}
// 恢复学员状态
$result = Db::name('person_course_schedule')
->where('id', $enrollment['id'])
->update([
'status' => 0, // 恢复为待上课状态
'updated_at' => date('Y-m-d H:i:s'),
'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 batchSignIn(array $data)
{
try {
// 验证必填字段
if (empty($data['schedule_id'])) {
return [
'code' => 0,
'msg' => '课程安排ID不能为空'
];
}
if (empty($data['students']) || !is_array($data['students'])) {
return [
'code' => 0,
'msg' => '学员签到数据不能为空'
];
}
// 开启事务
Db::startTrans();
$scheduleId = $data['schedule_id'];
$students = $data['students'];
$remark = $data['remark'] ?? '';
$classPhoto = $data['class_photo'] ?? '';
// 验证课程安排是否存在
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->where('deleted_at', 0)
->find();
if (empty($schedule)) {
Db::rollback();
return [
'code' => 0,
'msg' => '课程安排不存在或已被删除'
];
}
$successCount = 0;
$failedCount = 0;
$errors = [];
// 批量更新学员签到状态 - 优雅复用单个签到逻辑
foreach ($students as $index => $student) {
$studentId = $student['student_id'] ?? 0;
$resourceId = $student['resource_id'] ?? 0;
$status = $student['status'] ?? 1; // 默认为已到
try {
// 验证学员数据
if (empty($studentId) && empty($resourceId)) {
$failedCount++;
$errors[] = "" . ($index + 1) . "条记录:学员ID或资源ID不能为空";
continue;
}
// 使用内部签到处理方法,避免嵌套事务
$result = $this->processSingleStudentSignIn($scheduleId, $studentId, $resourceId, $status, $remark);
if ($result['success']) {
$successCount++;
} else {
$failedCount++;
$errors[] = "" . ($index + 1) . "条记录:" . $result['error'];
}
} catch (\Exception $e) {
$failedCount++;
$errors[] = "" . ($index + 1) . "条记录:处理异常 - " . $e->getMessage();
}
}
// 如果有课堂照片,保存到课程安排记录
if (!empty($classPhoto)) {
// 处理图片上传或保存逻辑
$updateScheduleData = [
'begin_img' => $classPhoto, // 使用 begin_img 字段存储课堂照片
'updated_at' => date('Y-m-d H:i:s')
];
// 如果有签到备注,也保存到课程安排的 remarks 字段
if (!empty($remark)) {
$updateScheduleData['remarks'] = $remark;
}
Db::name('course_schedule')
->where('id', $scheduleId)
->update($updateScheduleData);
}
// 记录签到历史(可选)
$this->recordSignInHistory($scheduleId, [
'total_students' => count($students),
'success_count' => $successCount,
'failed_count' => $failedCount,
'remark' => $remark,
'class_photo' => $classPhoto,
'sign_in_time' => date('Y-m-d H:i:s'),
'operator_id' => $this->user_id ?? 0
]);
// 检查是否有失败记录
if ($failedCount > 0) {
// 如果有失败记录,但也有成功记录,提示部分成功
if ($successCount > 0) {
Db::commit();
return [
'code' => 1,
'msg' => "批量签到部分成功,成功:{$successCount}人,失败:{$failedCount}",
'data' => [
'success_count' => $successCount,
'failed_count' => $failedCount,
'errors' => $errors
]
];
} else {
// 全部失败
Db::rollback();
return [
'code' => 0,
'msg' => "批量签到失败,失败:{$failedCount}",
'data' => [
'success_count' => $successCount,
'failed_count' => $failedCount,
'errors' => $errors
]
];
}
}
// 全部成功
Db::commit();
return [
'code' => 1,
'msg' => "批量签到成功,共签到{$successCount}",
'data' => [
'success_count' => $successCount,
'failed_count' => $failedCount,
'schedule_id' => $scheduleId
]
];
} catch (\Exception $e) {
Db::rollback();
return [
'code' => 0,
'msg' => '批量签到失败:' . $e->getMessage()
];
}
}
/**
* 记录签到历史
* @param int $scheduleId 课程安排ID
* @param array $signInData 签到数据
* @return void
*/
private function recordSignInHistory($scheduleId, $signInData)
{
try {
// 这里可以记录签到历史到专门的表中
// 如果没有专门的签到历史表,可以记录到日志或其他地方
$historyData = [
'schedule_id' => $scheduleId,
'sign_in_data' => json_encode($signInData),
'created_at' => date('Y-m-d H:i:s')
];
// 假设有一个签到历史表 sign_in_history
// Db::name('sign_in_history')->insert($historyData);
// 或者记录到系统日志
trace('Batch Sign In: ' . json_encode($historyData));
} catch (\Exception $e) {
// 记录日志失败不影响主流程
trace('Record sign in history failed: ' . $e->getMessage());
}
}
/**
* 处理试听课签到逻辑
* @param Student $student 学员模型实例
* @throws \Exception
*/
private function handleTrialClassCheckin($student)
{
try {
$currentTrialCount = $student->trial_class_count;
// 如果试听课次数为0,不进行任何更新
if ($currentTrialCount <= 0) {
return;
}
$updateData = [];
$newTrialCount = $currentTrialCount - 1;
$updateData['trial_class_count'] = max(0, $newTrialCount); // 确保最小值为0
// 根据当前试听次数设置对应的签到时间
if ($currentTrialCount === 2) {
// 第一次试听课签到
$updateData['first_come'] = date('Y-m-d H:i:s');
} elseif ($currentTrialCount === 1) {
// 第二次试听课签到
$updateData['second_come'] = date('Y-m-d H:i:s');
}
// 更新学员试听信息
$result = Student::where('id', $student->id)->update($updateData);
if ($result === false) {
throw new \Exception('更新学员试听信息失败');
}
// 记录日志
trace('Trial class checkin processed', 'info');
trace('Student details: ' . json_encode([
'student_id' => $student->id,
'old_trial_count' => $currentTrialCount,
'new_trial_count' => $updateData['trial_class_count'],
'update_fields' => array_keys($updateData)
]), 'info');
} catch (\Exception $e) {
// 抛出异常以便外层事务回滚
throw new \Exception('处理试听课签到失败:' . $e->getMessage());
}
}
/**
* 处理正式学员课程消减逻辑
* @param array $enrollment 学员课程安排记录
* @param Student $student 学员信息
* @throws \Exception
*/
private function handlePaidStudentCourseDeduction($enrollment, $student)
{
try {
$scheduleId = $enrollment['schedule_id'] ?? 0;
if (empty($scheduleId)) {
throw new \Exception('课程安排ID不能为空');
}
// 获取课程安排信息以确定课程日期
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->find();
if (!$schedule) {
throw new \Exception('找不到课程安排信息');
}
$courseDate = $schedule['course_date'];
// 根据课程日期动态查找应该核销的课程包
$studentCourse = $this->findApplicableStudentCourse($student->id, $courseDate);
if (!$studentCourse) {
throw new \Exception('找不到该日期有效的学员课程包');
}
// 获取课程信息以计算消耗课时数
$course = Db::name('course')
->where('id', $studentCourse['course_id'])
->find();
if (!$course) {
throw new \Exception('找不到课程信息');
}
// 计算本次消耗的课时数
$deductHours = $this->calculateCourseDeduction($course, $scheduleId);
// 检查剩余课时是否足够
$remainingHours = $studentCourse['total_hours'] - $studentCourse['use_total_hours'];
if ($remainingHours < $deductHours) {
throw new \Exception('剩余课时不足,当前剩余:' . $remainingHours . ',需要消耗:' . $deductHours);
}
// 1. 更新学员课程的已使用课时数
$updateResult = Db::name('student_courses')
->where('id', $studentCourse['id'])
->inc('use_total_hours', $deductHours)
->update(['updated_at' => date('Y-m-d H:i:s')]);
if (!$updateResult) {
throw new \Exception('更新学员课程课时失败');
}
// 2. 插入课程消减记录
$usageData = [
'student_course_id' => $studentCourse['id'],
'student_id' => $student->id,
'resource_id' => $enrollment['resources_id'] ?? null,
'used_hours' => $deductHours,
'usage_date' => date('Y-m-d'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
$usageResult = Db::name('student_course_usage')->insert($usageData);
if (!$usageResult) {
throw new \Exception('插入课程消减记录失败');
}
// 记录日志
trace('Paid student course deduction processed', 'info');
trace('Deduction details: ' . json_encode([
'student_id' => $student->id,
'student_course_id' => $studentCourse['id'],
'course_start_date' => $studentCourse['start_date'],
'course_end_date' => $studentCourse['end_date'],
'schedule_date' => $courseDate,
'deducted_hours' => $deductHours,
'remaining_hours' => $remainingHours - $deductHours,
'usage_date' => date('Y-m-d')
]), 'info');
} catch (\Exception $e) {
// 抛出异常以便外层事务回滚
throw new \Exception('处理正式学员课程消减失败:' . $e->getMessage());
}
}
/**
* 根据课程日期查找适用的学员课程包
* 优先选择:
* 1. 在有效期内(开始日期 ≤ 课程日期 ≤ 结束日期)
* 2. 还有剩余课时
* 3. 最早开始的课程包
* @param int $studentId 学员ID
* @param string $courseDate 课程日期
* @return array|null 学员课程记录
*/
private function findApplicableStudentCourse($studentId, $courseDate)
{
try {
// 查找在有效期内且有剩余课时的课程包
$applicableCourses = Db::name('student_courses')
->where('student_id', $studentId)
->where('status', 1) // 有效状态
->where('start_date', '<=', $courseDate) // 开始日期 <= 课程日期
->where('end_date', '>=', $courseDate) // 结束日期 >= 课程日期
->whereRaw('total_hours > use_total_hours') // 有剩余课时
->order('start_date ASC, created_at ASC') // 按开始日期升序,创建时间升序
->select()
->toArray();
if (empty($applicableCourses)) {
return null;
}
// 返回最早开始的有效课程包
return $applicableCourses[0];
} catch (\Exception $e) {
trace('Find applicable student course error: ' . $e->getMessage(), 'error');
return null;
}
}
/**
* 计算课程消耗课时数
* @param array $course 课程信息
* @param int $scheduleId 课程安排ID
* @return float 消耗课时数
*/
private function calculateCourseDeduction($course, $scheduleId)
{
try {
// 获取课程安排信息
$schedule = Db::name('course_schedule')
->where('id', $scheduleId)
->find();
if (!$schedule) {
throw new \Exception('找不到课程安排信息');
}
// 根据课程类型计算消耗课时数
switch ($course['course_type']) {
case 1: // 按课时
// 解析时间段计算实际课时
$timeSlot = $schedule['time_slot'] ?? '';
$deductHours = $this->parseTimeSlotToHours($timeSlot);
break;
case 2: // 按次卡
// 按次卡每次消耗1次
$deductHours = 1;
break;
case 3: // 按周期
// 周期课程每次消耗1次
$deductHours = 1;
break;
case 4: // 按时长
// 按实际时长计算,转换为小时
$duration = $course['duration'] ?? 0; // 分钟
$deductHours = $duration / 60;
break;
default:
// 默认每次消耗1课时
$deductHours = 1;
break;
}
// 确保返回正数
return max(0.01, $deductHours);
} catch (\Exception $e) {
// 计算失败时默认消耗1课时
return 1;
}
}
/**
* 解析时间段为小时数
* @param string $timeSlot 时间段,格式:08:00-08:30
* @return float 小时数
*/
private function parseTimeSlotToHours($timeSlot)
{
try {
if (empty($timeSlot) || strpos($timeSlot, '-') === false) {
return 1; // 默认1小时
}
$times = explode('-', $timeSlot);
if (count($times) !== 2) {
return 1;
}
$startTime = strtotime($times[0]);
$endTime = strtotime($times[1]);
if (!$startTime || !$endTime) {
return 1;
}
$minutes = ($endTime - $startTime) / 60;
$hours = $minutes / 60;
// 如果计算结果小于0.1小时,按0.1小时计算
return max(0.1, $hours);
} catch (\Exception $e) {
return 1; // 解析失败时默认1小时
}
}
/**
* 处理单个学员签到(内部方法,不管理事务)
* @param int $scheduleId 课程安排ID
* @param int $studentId 学员ID
* @param int $resourceId 资源ID
* @param int $status 签到状态
* @param string $reason 备注
* @return array 处理结果
*/
private function processSingleStudentSignIn($scheduleId, $personId, $resourceId, $status, $reason = '')
{
try {
// 查找学员课程安排记录
$enrollment = null;
// 优先使用student_id字段查询(前端传递的student_id)
if (!empty($personId)) {
// 首先尝试按student_id字段查询
$enrollment = Db::name('person_course_schedule')
->where('student_id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
// 如果按student_id没找到,再尝试按主键id查询
if (!$enrollment) {
$enrollment = Db::name('person_course_schedule')
->where('id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
// 如果还没找到,最后尝试按person_id字段查询
if (!$enrollment) {
$enrollment = Db::name('person_course_schedule')
->where('person_id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
} elseif (!empty($resourceId)) {
$enrollment = Db::name('person_course_schedule')
->where('resources_id', $resourceId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
if (empty($enrollment)) {
return ['success' => false, 'error' => '找不到学员记录'];
}
// 获取学员信息(用于试听课处理)
$student = Student::where('id', $enrollment['student_id'])->find();
if (!$student) {
return ['success' => false, 'error' => '找不到学员信息'];
}
// 检查体验课次数(仅对未付费学员且状态为签到时)
if ($status === 1 && $student->pay_status != 1) {
if ($student->trial_class_count <= 0) {
return ['success' => false, 'error' => '该学员体验课次数已用完,无法签到'];
}
}
// 准备更新数据
$updateData = [
'status' => $status,
'updated_at' => date('Y-m-d H:i:s')
];
if ($reason) {
$updateData['remark'] = $reason;
}
// 更新学员签到记录
$result = Db::name('person_course_schedule')
->where('id', $enrollment['id'])
->update($updateData);
if ($result === false) {
return ['success' => false, 'error' => '更新签到状态失败'];
}
// 处理签到后的课程消减逻辑
if ($status === 1) {
if ($student->pay_status != 1) {
// 处理试听课签到逻辑
$this->handleTrialClassCheckin($student);
} else {
// 处理正式学员的课程消减逻辑
$this->handlePaidStudentCourseDeduction($enrollment, $student);
}
}
return ['success' => true, 'error' => ''];
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* 删除学员所有未来课程安排
* @param array $data 删除参数
* @return array 删除结果
*/
public function deleteStudentFutureSchedules(array $data)
{
try {
$studentId = $data['student_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
$resourcesId = $data['resources_id'] ?? 0;
if (empty($studentId) && empty($personId) && empty($resourcesId)) {
return [
'code' => 0,
'msg' => '学员ID、人员ID或资源ID不能为空'
];
}
// 开启事务
Db::startTrans();
// 构建查询条件
$where = [
['course_date', '>=', date('Y-m-d')], // 只删除今天及未来的课程
['deleted_at', '=', 0]
];
// 根据传入的参数确定查询条件
if (!empty($studentId)) {
$where[] = ['student_id', '=', $studentId];
}
if (!empty($personId)) {
$where[] = ['person_id', '=', $personId];
}
if (!empty($resourcesId)) {
$where[] = ['resources_id', '=', $resourcesId];
}
// 查询要删除的课程安排数量
$count = Db::name('person_course_schedule')
->where($where)
->count();
if ($count == 0) {
Db::rollback();
return [
'code' => 1,
'msg' => '没有找到需要删除的未来课程安排',
'data' => ['deleted_count' => 0]
];
}
// 执行物理删除
$result = Db::name('person_course_schedule')
->where($where)
->delete();
if ($result === false) {
Db::rollback();
return [
'code' => 0,
'msg' => '删除课程安排失败'
];
}
// 记录删除日志
$this->recordDeleteScheduleLog([
'student_id' => $studentId,
'person_id' => $personId,
'resources_id' => $resourcesId,
'deleted_count' => $result,
'operator_id' => $this->user_id ?? 0,
'delete_time' => date('Y-m-d H:i:s'),
'reason' => $data['reason'] ?? '体验课次数用完,删除未来课程安排'
]);
// 提交事务
Db::commit();
return [
'code' => 1,
'msg' => "成功删除{$result}个未来课程安排",
'data' => [
'deleted_count' => $result
]
];
} catch (\Exception $e) {
Db::rollback();
return [
'code' => 0,
'msg' => '删除失败:' . $e->getMessage()
];
}
}
/**
* 记录删除课程安排日志
* @param array $logData 日志数据
*/
private function recordDeleteScheduleLog(array $logData)
{
try {
// 这里可以记录到日志表或系统日志
trace('Delete student future schedules: ' . json_encode($logData), 'info');
} catch (\Exception $e) {
// 日志记录失败不影响主流程
trace('Record delete schedule log failed: ' . $e->getMessage(), 'error');
}
}
}