diff --git a/niucloud/app/job/transfer/schedule/CourseScheduleJob.php b/niucloud/app/job/transfer/schedule/CourseScheduleJob.php index 57d3b11e..facad164 100644 --- a/niucloud/app/job/transfer/schedule/CourseScheduleJob.php +++ b/niucloud/app/job/transfer/schedule/CourseScheduleJob.php @@ -9,80 +9,46 @@ use think\facade\Log; class CourseScheduleJob extends BaseJob { /** - * 执行任务,将今天的自动排课复制到未来30天 + * 执行任务,将最新的自动排课复制到未来7天 + * @param mixed ...$data 任务参数(BaseJob要求) * @return array 处理结果 */ - public function doJob() + public function doJob(...$data) { - // 添加执行锁,防止重复执行 - $lockFile = runtime_path() . 'course_schedule.lock'; - if (file_exists($lockFile) && (time() - filemtime($lockFile)) < 600) { // 10分钟锁定 - Log::write('自动排课任务正在执行中,跳过'); - return ['status' => 'skipped', 'reason' => 'locked']; - } - - // 创建锁文件 - file_put_contents($lockFile, time()); + Log::write('开始执行自动排课任务'); try { - Log::write('开始执行自动排课任务'); - - // 检查今天是否已经执行过 - $today = date('Y-m-d'); - $executionFlag = runtime_path() . 'course_schedule_' . $today . '.flag'; - - if (file_exists($executionFlag)) { - Log::write('今天已经执行过自动排课,跳过'); - return ['status' => 'skipped', 'reason' => 'already_executed_today']; - } - - // 执行排课任务 - $result = $this->copyCoursesToFutureDays(30); + // 直接执行排课任务,生成未来7天的可预约时间段 + $result = $this->copyCoursesToFutureDays(7); - // 创建执行标记文件 - file_put_contents($executionFlag, time()); - - Log::write('自动排课任务执行完成'); + Log::write('自动排课任务执行完成,插入:' . $result['inserted'] . ',跳过:' . $result['skipped']); return $result; - } finally { - // 删除锁文件 - if (file_exists($lockFile)) { - unlink($lockFile); - } + + } catch (\Exception $e) { + Log::write('自动排课任务执行失败:' . $e->getMessage()); + throw $e; } } /** - * 将今天的自动排课复制到未来指定天数 - * @param int $days 未来天数 + * 将今天的自动排课复制到未来指定天数,生成可预约的课程时间段 + * @param int $days 未来天数,默认7天 * @return array 处理结果 */ - public function copyCoursesToFutureDays($days = 30) + public function copyCoursesToFutureDays($days = 7) { - // 获取基准日期 - 使用最近一个工作日的自动排课作为模板 - $baseDate = $this->getLatestAutoScheduleDate(); - - if (empty($baseDate)) { - Log::write('未找到自动排课模板数据'); - return [ - 'status' => 'failed', - 'reason' => 'no_template_data', - 'total' => 0, - 'inserted' => 0, - 'skipped' => 0 - ]; - } + $today = date('Y-m-d'); - // 获取基准日期的所有auto_schedule=1的课程 + // 获取今天所有auto_schedule=1的课程 $autoSchedules = CourseSchedule::where('auto_schedule', 1) - ->where('course_date', $baseDate) + ->where('course_date', $today) ->select(); - Log::write('找到' . count($autoSchedules) . '个基于日期 ' . $baseDate . ' 的自动排课模板'); + Log::write('找到' . count($autoSchedules) . '个今日自动排课模板'); $results = [ 'status' => 'success', - 'base_date' => $baseDate, + 'base_date' => $today, 'total' => count($autoSchedules), 'inserted' => 0, 'skipped' => 0, @@ -90,11 +56,11 @@ class CourseScheduleJob extends BaseJob ]; if (count($autoSchedules) == 0) { - Log::write('没有找到自动排课模板,跳过执行'); + Log::write('今日没有找到自动排课,跳过执行'); return $results; } - // 遍历每个课程,复制到未来30天 + // 遍历每个自动排课计划,为未来指定天数生成可预约时间段 foreach ($autoSchedules as $schedule) { $courseResults = $this->copyCourseToFutureDays($schedule, $days); $results['inserted'] += $courseResults['inserted']; @@ -254,35 +220,4 @@ class CourseScheduleJob extends BaseJob return $newSchedule; } - - /** - * 获取最近一个有自动排课数据的日期 - * @return string|null 最近的自动排课日期 - */ - protected function getLatestAutoScheduleDate() - { - // 查找最近7天内有自动排课的日期 - $latestDate = CourseSchedule::where('auto_schedule', 1) - ->where('course_date', '>=', date('Y-m-d', strtotime('-7 days'))) - ->where('course_date', '<=', date('Y-m-d')) - ->order('course_date', 'desc') - ->value('course_date'); - - if ($latestDate) { - Log::write('使用日期 ' . $latestDate . ' 作为自动排课模板'); - return $latestDate; - } - - // 如果最近7天没有,则查找历史数据中最新的 - $latestDate = CourseSchedule::where('auto_schedule', 1) - ->order('course_date', 'desc') - ->value('course_date'); - - if ($latestDate) { - Log::write('使用历史日期 ' . $latestDate . ' 作为自动排课模板'); - return $latestDate; - } - - return null; - } } \ No newline at end of file diff --git a/niucloud/app/service/api/apiService/CourseScheduleService.php b/niucloud/app/service/api/apiService/CourseScheduleService.php index 2cd6093f..6818bc65 100644 --- a/niucloud/app/service/api/apiService/CourseScheduleService.php +++ b/niucloud/app/service/api/apiService/CourseScheduleService.php @@ -915,6 +915,255 @@ class CourseScheduleService extends BaseApiService } } + /** + * 更新课程安排 + * @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 场地信息 @@ -982,4 +1231,428 @@ class CourseScheduleService extends BaseApiService 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() + ]; + } + } } \ No newline at end of file