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.
1658 lines
54 KiB
1658 lines
54 KiB
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | Niucloud-admin 企业快速开发的多应用管理平台
|
|
// +----------------------------------------------------------------------
|
|
// | 官方网址:https://www.niucloud.com
|
|
// +----------------------------------------------------------------------
|
|
// | niucloud团队 版权所有 开源版本可自由商用
|
|
// +----------------------------------------------------------------------
|
|
// | Author: Niucloud Team
|
|
// +----------------------------------------------------------------------
|
|
|
|
namespace app\service\api\apiService;
|
|
|
|
use core\base\BaseApiService;
|
|
use think\facade\Db;
|
|
|
|
/**
|
|
* 课程安排服务类
|
|
*/
|
|
class CourseScheduleService extends BaseApiService
|
|
{
|
|
// 定义表前缀,可以更方便地引用数据表
|
|
protected $prefix = 'school_';
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* 获取课程安排列表(支持多维度筛选)
|
|
* @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')
|
|
->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'
|
|
])
|
|
->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.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();
|
|
|
|
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
|
|
];
|
|
|
|
// 班级信息暂不存储在课程安排表中,根据实际需求可以通过关联表处理
|
|
|
|
// 如果有备注,则添加
|
|
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['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()
|
|
];
|
|
}
|
|
}
|
|
}
|