From ff6e4598845516f354ae1983694f07dec16cea65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B2=A9=E4=BB=9488?= <> Date: Thu, 12 Mar 2026 17:30:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=B6=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/Services/Admin/YeWu/RosterService.php | 181 +++++++++++++++++- .../Yewu/DepartmentResourcesSave.vue | 8 +- .../src/views/YeWu/DepartmentResources.vue | 2 +- 3 files changed, 182 insertions(+), 9 deletions(-) diff --git a/Laravel/app/Services/Admin/YeWu/RosterService.php b/Laravel/app/Services/Admin/YeWu/RosterService.php index 8315df8..2dd0021 100644 --- a/Laravel/app/Services/Admin/YeWu/RosterService.php +++ b/Laravel/app/Services/Admin/YeWu/RosterService.php @@ -10,6 +10,9 @@ use Exception; class RosterService { + // 资源缓存,避免重复查询 + private $resourceCache = []; + public function generatePlans($dateRange, $planModelIds, $userId, $dateType = 1, $holidayEnable = 1) { // 1. 基础参数校验 @@ -39,6 +42,16 @@ class RosterService $start_date = new DateTime($dateRange[0]); $end_date = new DateTime($dateRange[1]); + // ========================================== + // 【新增】1. 整体校验:模板与资源时令配置匹配 + // ========================================== + $this->validateTemplates($models); + + // ========================================== + // 【新增】2. 整体校验:资源时段冲突检测 + // ========================================== + $this->checkResourceTimeConflict($models, $start_date, $end_date); + // ========================================== // 【核心】3. 重复性检测 (在事务外执行,提高性能) // ========================================== @@ -70,13 +83,13 @@ class RosterService // --- 逻辑判断:节假日过滤 --- - // 如果是“仅节假日”模式 (date_type == 2),且当天不是节假日 -> 跳过 + // 如果是"仅节假日"模式 (date_type == 2),且当天不是节假日 -> 跳过 if ($dateType == 2 && !in_array($current_date_str, $holiday_list)) { $current_date->modify('+1 day'); continue; } - // 如果“节假日不可用” (HolidayEnable == 0),且当天是节假日 -> 跳过 + // 如果"节假日不可用" (HolidayEnable == 0),且当天是节假日 -> 跳过 if ($holidayEnable == 0 && in_array($current_date_str, $holiday_list)) { $current_date->modify('+1 day'); continue; @@ -88,13 +101,19 @@ class RosterService foreach ($models as $model) { // --- 逻辑判断:星期匹配 --- - // 如果是“按星期”模式 (date_type == 1) 且模板也是按星期定义的,必须星期一致 + // 如果是"按星期"模式 (date_type == 1) 且模板也是按星期定义的,必须星期一致 if ($dateType == 1 && isset($model->date_type) && $model->date_type == 1) { if ($model->weekname !== $weekname) { continue; } } + // 【新增】时令校验:跳过不在时令范围内的日期 + $resource = $this->getCachedResource($model->resources_id); + if (!$this->isDateInSeasonalRange($current_date, $model->type, $resource)) { + continue; // 跳过该模板在此日期的生成 + } + // --- 构造插入数据 --- $data = [ 'roster_id' => $model->id, @@ -153,6 +172,160 @@ class RosterService } } + /** + * 整体校验:模板与资源时令配置必须严格匹配 + * - 模板 type=1/2 → 资源 time_mode=1 + * - 模板 type=0 → 资源 time_mode=0 + */ + private function validateTemplates($models) + { + foreach ($models as $model) { + // 获取资源信息 + $resource = $this->getCachedResource($model->resources_id); + + if (!$resource) { + throw new Exception("模板关联的资源不存在,模板ID: {$model->id}"); + } + + // 严格一对一匹配 + if ($model->type != 0 && $resource->time_mode != 1) { + $errorMsg = "模板ID {$model->id} 配置了时令(type={$model->type}),但资源未开启时令,请检查资源配置!"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + if ($model->type == 0 && $resource->time_mode != 0) { + $errorMsg = "模板ID {$model->id} 未配置时令(type=0),但资源开启了时令,请检查资源配置!"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + } + } + + /** + * 资源级别查重:同一资源同一天的时间段不能重叠 + * 同一资源同一天可以有多个不同时段,但是时段不能交差 + * 边界判断:不重叠(一个时间点结束,另一个可以开始) + */ + private function checkResourceTimeConflict($models, $startDate, $endDate) + { + $startStr = $startDate->format('Y-m-d'); + $endStr = $endDate->format('Y-m-d'); + + foreach ($models as $model) { + // 查询该资源在日期范围内已存在的排班 + $existingPlans = DB::table('s_source_roster_detail') + ->where('resources_id', $model->resources_id) + ->where('date', '>=', $startStr) + ->where('date', '<=', $endStr) + ->where('is_del', 0) + ->get(); + + foreach ($existingPlans as $existing) { + // 检查时间是否重叠(同一天才检查) + $existingDate = $existing->date; + // 使用模板的星期作为参考日期 + $templateDate = $this->getTemplateDate($model); + if ($existingDate === $templateDate) { + if ($this->isTimeOverlap( + $model->begin_time, + $model->end_time, + $existing->begin_time, + $existing->end_time + )) { + throw new Exception( + "资源ID [{$model->resources_id}] 在 {$existingDate} 存在时段冲突!" . + "新增: {$model->begin_time}-{$model->end_time} " . + "与已存在的 {$existing->begin_time}-{$existing->end_time} 重叠" + ); + } + } + } + } + } + + /** + * 判断两个时段是否重叠 + * 不重叠返回 false + * 边界判断:不重叠(一个时间点结束,另一个可以开始) + * 时段1: [start1, end1) 时段2: [start2, end2) + * 重叠条件:时段1的结束时间 > 时段2的开始时间 && 时段2的结束时间 > 时段1的开始时间 + */ + private function isTimeOverlap($start1, $end1, $start2, $end2) + { + return ($end1 > $start2) && ($end2 > $start1); + } + + /** + * 时令日期匹配:判断日期是否在时令范围内 + * - 如果资源 time_mode != 1 或模板 type == 0,不需要时令校验,返回 true + * - 否则检查日期是否在对应季节的周期范围内 + */ + private function isDateInSeasonalRange($currentDate, $modelType, $resource) + { + // 不需要时令校验 + if ($resource->time_mode != 1 || $modelType == 0) { + return true; + } + + $currentMMDD = $currentDate->format('m-d'); + + // 解析 time_range(从缓存中获取) + $time_range = json_decode($resource->time_range, true); + + if (!$time_range || !is_array($time_range)) { + return false; + } + + // 遍历所有季节周期 + foreach ($time_range as $season) { + // 转换类型进行匹配(字符串 "1"/"2" vs 整数 1/2) + if ((string)($season['type'] ?? '') === (string)$modelType) { + // 检查是否在任意周期范围内 + if (isset($season['periods']) && is_array($season['periods'])) { + foreach ($season['periods'] as $period) { + $start = $period['start'] ?? ''; + $end = $period['end'] ?? ''; + + if (!empty($start) && !empty($end) && + $currentMMDD >= $start && $currentMMDD <= $end) { + return true; // 匹配成功 + } + } + } + } + } + + return false; // 不在时令范围内 + } + + /** + * 获取缓存的资源信息,避免重复查询 + */ + private function getCachedResource($resourceId) + { + if (!isset($this->resourceCache[$resourceId])) { + $this->resourceCache[$resourceId] = DB::table('s_department_resources') + ->where('id', $resourceId) + ->first(); + } + return $this->resourceCache[$resourceId]; + } + + /** + * 获取模板的日期(用于资源查重时的日期比较) + * 注意:这里仅用于比较,实际生成时按循环中的日期 + */ + private function getTemplateDate($model) + { + // 如果模板有固定日期,返回固定日期 + if (isset($model->date)) { + return $model->date; + } + // 否则返回 null(表示需要按循环日期比较) + return null; + } + /** * 独立的重复检测方法 * 如果发现重复,直接抛出包含详细信息的异常 @@ -193,7 +366,7 @@ class RosterService $msglist .= $item->date . ' '; $msgIds .= $item->id . ' '; // 拼接模板信息 - $msg .= " " . $model->weekname . $model->begin_time . '-' . $model->end_time . " "; + $msg .= "" . $model->weekname . $model->begin_time . '-' . $model->end_time . " "; } } } diff --git a/YiJi-admin/src/components/Yewu/DepartmentResourcesSave.vue b/YiJi-admin/src/components/Yewu/DepartmentResourcesSave.vue index 142d77b..e70d264 100644 --- a/YiJi-admin/src/components/Yewu/DepartmentResourcesSave.vue +++ b/YiJi-admin/src/components/Yewu/DepartmentResourcesSave.vue @@ -99,12 +99,12 @@ // 时令数据 const timeRangeList = ref([ { - type: 'summer', + type: '1', type_name: '夏季排班', periods: [['', '']] }, { - type: 'winter', + type: '2', type_name: '冬季排班', periods: [['', '']] } @@ -146,12 +146,12 @@ // 如果开启时令且 timeRangeList 为空,才初始化默认值 timeRangeList.value = [ { - type: 'summer', + type: '1', type_name: '夏季排班', periods: [['', '']] }, { - type: 'winter', + type: '2', type_name: '冬季排班', periods: [['', '']] } diff --git a/YiJi-admin/src/views/YeWu/DepartmentResources.vue b/YiJi-admin/src/views/YeWu/DepartmentResources.vue index fb94bfc..60cc10f 100644 --- a/YiJi-admin/src/views/YeWu/DepartmentResources.vue +++ b/YiJi-admin/src/views/YeWu/DepartmentResources.vue @@ -43,7 +43,7 @@ 开启
- 夏: + 夏: 冬: {{ period.start }}~{{ period.end }}