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.
345 lines
13 KiB
345 lines
13 KiB
<?php
|
|
|
|
namespace app\job\transfer\schedule;
|
|
|
|
use app\model\course_schedule\CourseSchedule;
|
|
use core\base\BaseJob;
|
|
use think\facade\Log;
|
|
|
|
class CourseScheduleJob extends BaseJob
|
|
{
|
|
/**
|
|
* 执行任务,将自动排课模板复制到未来7天
|
|
* @param mixed ...$data 任务参数(BaseJob要求)
|
|
* @return array 处理结果
|
|
*/
|
|
public function doJob(...$data)
|
|
{
|
|
Log::write('开始执行自动排课任务');
|
|
|
|
try {
|
|
// 执行排课任务,生成未来7天的可预约时间段
|
|
$result = $this->copyCoursesToFutureDays(7);
|
|
|
|
Log::write('自动排课任务执行完成,插入:' . $result['inserted'] . ',跳过:' . $result['skipped']);
|
|
return true;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::write('自动排课任务执行失败:' . $e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将自动排课模板复制到未来指定天数,生成可预约的课程时间段
|
|
* @param int $days 未来天数,默认7天
|
|
* @return array 处理结果
|
|
*/
|
|
public function copyCoursesToFutureDays($days = 7)
|
|
{
|
|
// 获取所有auto_schedule=1的课程作为模板(不限制日期)
|
|
$autoSchedules = CourseSchedule::where('auto_schedule', 1)
|
|
->select();
|
|
|
|
Log::write('找到' . count($autoSchedules) . '个自动排课模板');
|
|
|
|
$results = [
|
|
'status' => 'success',
|
|
'total' => count($autoSchedules),
|
|
'inserted' => 0,
|
|
'skipped' => 0,
|
|
'user_modified' => 0,
|
|
'details' => []
|
|
];
|
|
|
|
if (count($autoSchedules) == 0) {
|
|
Log::write('没有找到自动排课模板,跳过执行');
|
|
return $results;
|
|
}
|
|
|
|
// 遍历每个自动排课模板,为未来指定天数生成可预约时间段
|
|
foreach ($autoSchedules as $schedule) {
|
|
$courseResults = $this->copyCourseToFutureDays($schedule, $days);
|
|
$results['inserted'] += $courseResults['inserted'];
|
|
$results['skipped'] += $courseResults['skipped'];
|
|
$results['user_modified'] += $courseResults['user_modified'];
|
|
$results['details'][] = $courseResults;
|
|
}
|
|
|
|
Log::write('自动排课完成,共插入' . $results['inserted'] . '个课程,跳过' . $results['skipped'] . '个已存在课程,用户干预' . $results['user_modified'] . '个课程');
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* 将单个课程复制到未来指定天数
|
|
* @param CourseSchedule $schedule 课程安排
|
|
* @param int $days 未来天数
|
|
* @return array 处理结果
|
|
*/
|
|
protected function copyCourseToFutureDays(CourseSchedule $schedule, $days)
|
|
{
|
|
$result = [
|
|
'course_id' => $schedule->course_id,
|
|
'campus_id' => $schedule->campus_id,
|
|
'venue_id' => $schedule->venue_id,
|
|
'time_slot' => $schedule->time_slot,
|
|
'inserted' => 0,
|
|
'skipped' => 0,
|
|
'user_modified' => 0,
|
|
'dates' => []
|
|
];
|
|
|
|
// 从明天开始,复制到未来指定天数
|
|
for ($i = 1; $i <= $days; $i++) {
|
|
// 计算目标日期
|
|
$targetDate = date('Y-m-d', strtotime("+{$i} days"));
|
|
|
|
// 检查该日期是否有相同的课程安排
|
|
$checkResult = $this->checkCourseExists($schedule, $targetDate);
|
|
|
|
if (!$checkResult['exists']) {
|
|
// 如果不存在,则插入新的课程安排
|
|
$newSchedule = $this->createNewSchedule($schedule, $targetDate);
|
|
$result['inserted']++;
|
|
$result['dates'][] = [
|
|
'date' => $targetDate,
|
|
'status' => 'inserted',
|
|
'id' => $newSchedule->id
|
|
];
|
|
} elseif ($checkResult['skip_reason'] === 'user_modified') {
|
|
// 用户已手动修改此课程,跳过并记录
|
|
$result['user_modified']++;
|
|
$result['dates'][] = [
|
|
'date' => $targetDate,
|
|
'status' => 'user_modified',
|
|
'reason' => '用户已取消自动排课',
|
|
'existing_id' => $checkResult['existing_id']
|
|
];
|
|
|
|
// 记录用户干预的日志
|
|
Log::write("用户干预检测:课程ID {$schedule->course_id} 在 {$targetDate} 已被用户手动修改,跳过自动排课");
|
|
} else {
|
|
// 课程已存在(自动排课生成的)
|
|
$result['skipped']++;
|
|
$result['dates'][] = [
|
|
'date' => $targetDate,
|
|
'status' => 'auto_exists',
|
|
'reason' => '自动排课已存在',
|
|
'existing_id' => $checkResult['existing_id']
|
|
];
|
|
}
|
|
}
|
|
|
|
// 记录用户干预统计
|
|
if ($result['user_modified'] > 0) {
|
|
Log::write("课程ID {$schedule->course_id}:用户干预了 {$result['user_modified']} 个时间段,尊重用户选择跳过自动排课");
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 处理自动排课数据,按月份检查并插入不存在的课程
|
|
* @param int $month 月份(1-12)
|
|
* @param int $year 年份
|
|
* @return array 处理结果
|
|
*/
|
|
public function processAutoSchedule($month, $year)
|
|
{
|
|
// 获取所有auto_schedule=1的课程作为模板
|
|
$autoSchedules = CourseSchedule::where('auto_schedule', 1)->select();
|
|
|
|
$results = [];
|
|
foreach ($autoSchedules as $schedule) {
|
|
// 获取该月份的所有日期
|
|
$daysInMonth = date('t', mktime(0, 0, 0, $month, 1, $year));
|
|
|
|
for ($day = 1; $day <= $daysInMonth; $day++) {
|
|
$courseDate = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
|
|
|
// 检查该日期是否有相同的课程安排
|
|
$checkResult = $this->checkCourseExists($schedule, $courseDate);
|
|
|
|
if (!$checkResult['exists']) {
|
|
// 如果不存在,则插入新的课程安排
|
|
$newSchedule = $this->createNewSchedule($schedule, $courseDate);
|
|
$results[] = [
|
|
'status' => 'inserted',
|
|
'schedule' => $newSchedule,
|
|
'date' => $courseDate
|
|
];
|
|
} elseif ($checkResult['skip_reason'] === 'user_modified') {
|
|
// 用户已手动修改此课程
|
|
$results[] = [
|
|
'status' => 'user_modified',
|
|
'schedule' => $schedule,
|
|
'date' => $courseDate,
|
|
'reason' => '用户已取消自动排课'
|
|
];
|
|
} else {
|
|
// 课程已存在(自动排课生成的)
|
|
$results[] = [
|
|
'status' => 'auto_exists',
|
|
'schedule' => $schedule,
|
|
'date' => $courseDate
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
|
|
/**
|
|
* 检查指定日期是否有相同的课程安排,或者用户已取消自动排课
|
|
* @param CourseSchedule $schedule 原始课程安排
|
|
* @param string $courseDate 课程日期
|
|
* @return array 检查结果 ['exists' => bool, 'auto_schedule' => bool, 'skip_reason' => string]
|
|
*/
|
|
protected function checkCourseExists(CourseSchedule $schedule, $courseDate)
|
|
{
|
|
$existing = CourseSchedule::where(function ($query) use ($schedule, $courseDate) {
|
|
$query->where('campus_id', $schedule->campus_id)
|
|
->where('venue_id', $schedule->venue_id)
|
|
->where('course_date', $courseDate)
|
|
->where('time_slot', $schedule->time_slot)
|
|
->where('course_id', $schedule->course_id);
|
|
})->find();
|
|
|
|
if (!$existing) {
|
|
return ['exists' => false, 'auto_schedule' => false, 'skip_reason' => ''];
|
|
}
|
|
|
|
// 如果课程存在但auto_schedule=0,说明用户已手动修改,跳过自动排课
|
|
if ($existing->auto_schedule == 0) {
|
|
return [
|
|
'exists' => true,
|
|
'auto_schedule' => false,
|
|
'skip_reason' => 'user_modified',
|
|
'existing_id' => $existing->id
|
|
];
|
|
}
|
|
|
|
// 课程存在且仍是自动排课状态
|
|
return [
|
|
'exists' => true,
|
|
'auto_schedule' => true,
|
|
'skip_reason' => 'auto_exists',
|
|
'existing_id' => $existing->id
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 创建新的课程安排
|
|
* @param CourseSchedule $schedule 原始课程安排
|
|
* @param string $courseDate 课程日期
|
|
* @return CourseSchedule 新创建的课程安排
|
|
*/
|
|
protected function createNewSchedule(CourseSchedule $schedule, $courseDate)
|
|
{
|
|
$newSchedule = new CourseSchedule();
|
|
$newSchedule->campus_id = $schedule->campus_id;
|
|
$newSchedule->venue_id = $schedule->venue_id;
|
|
$newSchedule->course_date = $courseDate;
|
|
$newSchedule->time_slot = $schedule->time_slot;
|
|
$newSchedule->course_id = $schedule->course_id;
|
|
$newSchedule->auto_schedule = 1;
|
|
$newSchedule->created_by = 'system';
|
|
|
|
// 解析时间段并填充start_time和end_time
|
|
$timeData = $this->parseTimeSlot($schedule->time_slot);
|
|
$newSchedule->start_time = $timeData ? $timeData['start_time'] : null;
|
|
$newSchedule->end_time = $timeData ? $timeData['end_time'] : null;
|
|
|
|
// 根据课程日期确定初始状态
|
|
$newSchedule->status = $this->determineInitialStatus($courseDate, $timeData);
|
|
|
|
// 复制其他所有字段(除了id和主键相关字段)
|
|
$attributes = $schedule->toArray();
|
|
foreach ($attributes as $key => $value) {
|
|
// 跳过id、主键相关字段和我们已经设置的字段
|
|
if (!in_array($key, ['id', 'course_date', 'auto_schedule', 'start_time', 'end_time', 'status', 'created_by'])) {
|
|
$newSchedule->$key = $value;
|
|
}
|
|
}
|
|
|
|
$newSchedule->save();
|
|
|
|
return $newSchedule;
|
|
}
|
|
|
|
/**
|
|
* 解析time_slot字符串,提取开始和结束时间
|
|
* @param string $timeSlot 格式如 "09:00-10:30"
|
|
* @return array|null
|
|
*/
|
|
private function parseTimeSlot($timeSlot)
|
|
{
|
|
if (empty($timeSlot)) {
|
|
return null;
|
|
}
|
|
|
|
// 支持多种格式:09:00-10:30, 09:00~10:30, 9:00-10:30
|
|
if (preg_match('/(\d{1,2}:\d{2})\s*[-~~]\s*(\d{1,2}:\d{2})/', $timeSlot, $matches)) {
|
|
return [
|
|
'start_time' => $matches[1],
|
|
'end_time' => $matches[2]
|
|
];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 根据课程日期和时间段确定初始状态
|
|
* @param string $courseDate 课程日期
|
|
* @param array|null $timeData 时间数据
|
|
* @return string
|
|
*/
|
|
private function determineInitialStatus($courseDate, $timeData)
|
|
{
|
|
$currentDate = date('Y-m-d');
|
|
$currentTime = date('H:i:s');
|
|
|
|
// 如果是过去的日期,直接标记为已完成
|
|
if ($courseDate < $currentDate) {
|
|
return 'completed';
|
|
}
|
|
|
|
// 如果是未来的日期,标记为待安排
|
|
if ($courseDate > $currentDate) {
|
|
return 'pending';
|
|
}
|
|
|
|
// 如果是今天且有时间数据,根据时间判断状态
|
|
if ($courseDate === $currentDate && $timeData) {
|
|
$startTime = $timeData['start_time'];
|
|
$endTime = $timeData['end_time'];
|
|
|
|
$currentTimestamp = strtotime($currentTime);
|
|
$startTimestamp = strtotime($startTime);
|
|
$endTimestamp = strtotime($endTime);
|
|
|
|
// 如果当前时间在课程时间段内,状态为进行中
|
|
if ($currentTimestamp >= $startTimestamp && $currentTimestamp <= $endTimestamp) {
|
|
return 'ongoing';
|
|
}
|
|
|
|
// 如果课程已结束,状态为已完成
|
|
if ($currentTimestamp > $endTimestamp) {
|
|
return 'completed';
|
|
}
|
|
|
|
// 如果距离开始时间不足6小时,状态为即将开始
|
|
$timeDiff = $startTimestamp - $currentTimestamp;
|
|
if ($timeDiff <= 6 * 3600 && $timeDiff > 0) {
|
|
return 'upcoming';
|
|
}
|
|
}
|
|
|
|
// 其他情况默认为待安排
|
|
return 'pending';
|
|
}
|
|
}
|