diff --git a/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php b/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php index 31b9323..4733a45 100644 --- a/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php +++ b/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php @@ -402,135 +402,190 @@ public function BatchGenerateByDateRange(Request $request, RosterService $roster { $dateRange = request('dateRange'); $HolidayEnable = request('HolidayEnable', 1); + $autoMode = request('autoMode', 1); $userid = 0; try { - // 1. 查询所有资源,按 time_mode 分组 - $resources = DB::table('s_department_resources') - ->where(['is_del' => 0]) - ->get() - ->groupBy('time_mode'); - - $results = []; - $totalCount = 0; - - // 2. 遍历不同 time_mode 的资源 - foreach ($resources as $timeMode => $resourceGroup) { - $resourceIds = $resourceGroup->pluck('id')->toArray(); - - // 3. 根据 time_mode 查询对应的模板 - $templatesQuery = DB::table('s_source_roster') - ->leftJoin('s_department_resources', 's_source_roster.resources_id', '=', 's_department_resources.id') - ->select( - 's_source_roster.*', - 's_department_resources.department_resources_name', - 's_department_resources.time_mode', - 's_department_resources.time_range' - ) - ->whereIn('s_source_roster.resources_id', $resourceIds) - ->where(['s_source_roster.status' => 1, 's_source_roster.is_del' => 0]); - - if ($timeMode == 1) { - // 时令资源: 只查询 type=1 或 2 的模板 - $templatesQuery->whereIn('s_source_roster.type', [1, 2]); - } else { - // 普通资源: 只查询 type=0 的模板 - $templatesQuery->where('s_source_roster.type', 0); - } + if ($autoMode == 1) { + // 自动模式:按顶级科室的 auto_gen_days 生成 + $depts = DB::table('s_department') + ->where('pid', 0) + ->where('is_del', 0) + ->where('appointment_enabled', 1) + ->where('auto_gen_days', '>', 0) + ->get(); + + $allResults = []; + $allTotal = 0; + + foreach ($depts as $dept) { + $today = date('Y-m-d'); + $endDate = date('Y-m-d', strtotime("+{$dept->auto_gen_days} days")); + $deptDateRange = [$today, $endDate]; + + $resources = DB::table('s_department_resources') + ->where('department_id', $dept->id) + ->where('is_del', 0) + ->get() + ->groupBy('time_mode'); + + if ($resources->isEmpty()) { + continue; + } - $allModels = $templatesQuery->get(); + $res = $this->generateBatchForResources( + $resources, $deptDateRange, $HolidayEnable, $userid, $rosterService + ); - if ($allModels->isEmpty()) { - continue; // 该 time_mode 下没有模板,跳过 + $allResults = array_merge($allResults, $res['details']); + $allTotal += $res['total_count']; } - // 4. 时令模板根据资源的 time_range 进行匹配 - if ($timeMode == 1) { - $validTemplates = collect(); - $startDate = new DateTime($dateRange[0]); - $endDate = new DateTime($dateRange[1]); - $currentDate = clone $startDate; - - while ($currentDate <= $endDate) { - foreach ($allModels as $model) { - $resource = $resourceGroup->where('id', $model->resources_id)->first(); - - // 检查日期是否在时令范围内 - if ($this->isDateInSeasonalRange($currentDate, $model->type, $resource)) { - if (!$validTemplates->contains('id', $model->id)) { - $validTemplates->push($model); - } + $successCount = collect($allResults)->where('status', 'success')->count(); + $failedCount = collect($allResults)->where('status', 'failed')->count(); + + return \Yz::Return( + true, + "自动生成完成,共 {$allTotal} 条 (成功: {$successCount} 资源, 失败: {$failedCount} 资源)", + [ + 'total_count' => $allTotal, + 'success_count' => $successCount, + 'failed_count' => $failedCount, + 'details' => $allResults + ] + ); + } else { + // 手动模式:使用入参 dateRange,走原有逻辑 + $resources = DB::table('s_department_resources') + ->where(['is_del' => 0]) + ->get() + ->groupBy('time_mode'); + + $res = $this->generateBatchForResources( + $resources, $dateRange, $HolidayEnable, $userid, $rosterService + ); + + return \Yz::Return( + true, + "批量生成完成,共 {$res['total_count']} 条 (成功: {$res['success_count']} 资源, 失败: {$res['failed_count']} 资源)", + $res + ); + } + + } catch (\Exception $e) { + return \Yz::echoError1($e->getMessage()); + } + } + + /** + * 批量生成号源(核心逻辑,按资源分组遍历) + */ + private function generateBatchForResources($resources, $dateRange, $HolidayEnable, $userid, RosterService $rosterService) + { + $results = []; + $totalCount = 0; + + foreach ($resources as $timeMode => $resourceGroup) { + $resourceIds = $resourceGroup->pluck('id')->toArray(); + + $templatesQuery = DB::table('s_source_roster') + ->leftJoin('s_department_resources', 's_source_roster.resources_id', '=', 's_department_resources.id') + ->select( + 's_source_roster.*', + 's_department_resources.department_resources_name', + 's_department_resources.time_mode', + 's_department_resources.time_range' + ) + ->whereIn('s_source_roster.resources_id', $resourceIds) + ->where(['s_source_roster.status' => 1, 's_source_roster.is_del' => 0]); + + if ($timeMode == 1) { + $templatesQuery->whereIn('s_source_roster.type', [1, 2]); + } else { + $templatesQuery->where('s_source_roster.type', 0); + } + + $allModels = $templatesQuery->get(); + + if ($allModels->isEmpty()) { + continue; + } + + if ($timeMode == 1) { + $validTemplates = collect(); + $startDate = new DateTime($dateRange[0]); + $endDate = new DateTime($dateRange[1]); + $currentDate = clone $startDate; + + while ($currentDate <= $endDate) { + foreach ($allModels as $model) { + $resource = $resourceGroup->where('id', $model->resources_id)->first(); + + if ($this->isDateInSeasonalRange($currentDate, $model->type, $resource)) { + if (!$validTemplates->contains('id', $model->id)) { + $validTemplates->push($model); } } - $currentDate->modify('+1 day'); } + $currentDate->modify('+1 day'); + } - $allModels = $validTemplates; + $allModels = $validTemplates; - if ($allModels->isEmpty()) { - continue; // 没有匹配的时令模板,跳过 - } + if ($allModels->isEmpty()) { + continue; } + } - // 5. 按资源分组,按 date_type 细分,循环调用 generatePlans - $groupedByResource = $allModels->groupBy('resources_id'); - - foreach ($groupedByResource as $resourcesId => $models) { - $groupedByDateType = $models->groupBy('date_type'); - $resourceCount = 0; - - try { - foreach ($groupedByDateType as $dateType => $typeModels) { - $typeModelIds = $typeModels->pluck('id')->toArray(); - - $result = $rosterService->generatePlans( - $dateRange, - $typeModelIds, - $userid, - $dateType, - $HolidayEnable - ); - $resourceCount += $result['count']; - } + $groupedByResource = $allModels->groupBy('resources_id'); + + foreach ($groupedByResource as $resourcesId => $models) { + $groupedByDateType = $models->groupBy('date_type'); + $resourceCount = 0; + + try { + foreach ($groupedByDateType as $dateType => $typeModels) { + $typeModelIds = $typeModels->pluck('id')->toArray(); - $results[] = [ - 'resources_id' => $resourcesId, - 'resources_name' => $models->first()->department_resources_name ?? '', - 'status' => 'success', - 'count' => $resourceCount - ]; - $totalCount += $resourceCount; - - } catch (\Exception $e) { - $results[] = [ - 'resources_id' => $resourcesId, - 'resources_name' => $models->first()->department_resources_name ?? '', - 'status' => 'failed', - 'error' => $e->getMessage(), - 'count' => 0 - ]; + $result = $rosterService->generatePlans( + $dateRange, + $typeModelIds, + $userid, + $dateType, + $HolidayEnable + ); + $resourceCount += $result['count']; } + + $results[] = [ + 'resources_id' => $resourcesId, + 'resources_name' => $models->first()->department_resources_name ?? '', + 'status' => 'success', + 'count' => $resourceCount + ]; + $totalCount += $resourceCount; + + } catch (\Exception $e) { + $results[] = [ + 'resources_id' => $resourcesId, + 'resources_name' => $models->first()->department_resources_name ?? '', + 'status' => 'failed', + 'error' => $e->getMessage(), + 'count' => 0 + ]; } } + } - // 6. 统计结果 - $successCount = collect($results)->where('status', 'success')->count(); - $failedCount = collect($results)->where('status', 'failed')->count(); - - return \Yz::Return( - true, - "批量生成完成,共 {$totalCount} 条 (成功: {$successCount} 资源, 失败: {$failedCount} 资源)", - [ - 'total_count' => $totalCount, - 'success_count' => $successCount, - 'failed_count' => $failedCount, - 'details' => $results - ] - ); + $successCount = collect($results)->where('status', 'success')->count(); + $failedCount = collect($results)->where('status', 'failed')->count(); - } catch (\Exception $e) { - return \Yz::echoError1($e->getMessage()); - } + return [ + 'total_count' => $totalCount, + 'success_count' => $successCount, + 'failed_count' => $failedCount, + 'details' => $results + ]; } /** diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index a8761da..682e3bc 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -257,6 +257,101 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ $pp = array_merge($pl1, $pl2); + // 诊室排序:按最早可用时间升序 → 最早时段剩余号源降序 → 随机 + $grouped = []; + $nowTimestamp = time(); + foreach ($pp as $plan) { + $name = $plan->department_resources_name; + if (!isset($grouped[$name])) { + $grouped[$name] = ['plans' => [], 'remaining' => 0, 'earliest_remaining' => 0]; + } + $grouped[$name]['plans'][] = $plan; + + // 累计全天总剩余 + $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0) - ($plan->locked_count ?? 0); + $grouped[$name]['remaining'] += max($remaining, 0); + + // 判断是否未过期,收集最早开始时间及该时段剩余 + $endDateTime = $plan->date . ' ' . ($plan->end_time ?? '23:59:59'); + if (strtotime($endDateTime) > $nowTimestamp) { + $startDateTime = $plan->date . ' ' . ($plan->begin_time ?? '00:00:00'); + $ts = strtotime($startDateTime); + if (!isset($grouped[$name]['earliest']) || $ts < $grouped[$name]['earliest']) { + $grouped[$name]['earliest'] = $ts; + $grouped[$name]['earliest_remaining'] = max($remaining, 0); + } elseif ($ts == $grouped[$name]['earliest']) { + // 同一 earliest 时段,累加该时段剩余 + $grouped[$name]['earliest_remaining'] += max($remaining, 0); + } + } + } + + // 诊室排序:先转为索引数组,再手动排序 + $grouped = array_values($grouped); + if (count($grouped) >= 2) { + // 按规则排序:有可用号源优先 → 最早时间升序 → 剩余号源降序 → 随机 + $hasEarliest = []; + foreach ($grouped as $idx => $g) { + $hasEarliest[$idx] = isset($g['earliest']); + } + + // 简单冒泡排序(只有 2 个诊室时足够) + for ($i = 0; $i < count($grouped) - 1; $i++) { + for ($j = $i + 1; $j < count($grouped); $j++) { + $a = $grouped[$i]; + $b = $grouped[$j]; + $aHas = isset($a['earliest']); + $bHas = isset($b['earliest']); + $swap = false; + + if ($aHas != $bHas) { + $swap = $bHas; // 有可用号源的排前面 + } elseif ($aHas) { + if ($a['earliest'] != $b['earliest']) { + $swap = ($a['earliest'] > $b['earliest']); // 最早时间升序 + } elseif ($a['earliest_remaining'] != $b['earliest_remaining']) { + $swap = ($a['earliest_remaining'] < $b['earliest_remaining']); // 最早时段剩余号源降序 + } else { + $swap = (rand(0, 1) == 1); // 随机 + } + } else { + $swap = (rand(0, 1) == 1); // 都无可用号源,随机 + } + + if ($swap) { + $temp = $grouped[$i]; + $grouped[$i] = $grouped[$j]; + $grouped[$j] = $temp; + } + } + } + } + + // 按诊室排序后,同诊室 plan 按时间排序,最后打散 + $pp = []; + $debugGroupedOrder = []; + foreach ($grouped as $g) { + $plans = $g['plans']; + $debugGroupedOrder[] = [ + 'name' => $plans[0]->department_resources_name ?? '', + 'earliest' => isset($g['earliest']) ? date('Y-m-d H:i:s', $g['earliest']) : null, + 'remaining' => $g['remaining'], + ]; + usort($plans, function($a, $b) { + $timeA = $a->date . ' ' . ($a->begin_time ?? '00:00:00'); + $timeB = $b->date . ' ' . ($b->begin_time ?? '00:00:00'); + if ($timeA !== $timeB) { + return $timeA <=> $timeB; + } + $availableA = ($a->count ?? 0) - ($a->used_count ?? 0) - ($a->locked_count ?? 0); + $availableB = ($b->count ?? 0) - ($b->used_count ?? 0) - ($b->locked_count ?? 0); + return ($availableB <=> $availableA); + }); + foreach ($plans as $plan) { + $pp[] = $plan; + } + } + return \Yz::Return(true, '查询完成', [ 'today_date' => date("Y-m-d"), @@ -272,6 +367,7 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ 'debug_allRids' => $debugAllRids ?? [], 'debug_skipped' => $debugSkipped ?? [], 'debug_passedRids' => array_values(array_unique($debugPassed ?? [])), + 'debug_grouped_order' => $debugGroupedOrder ?? [], ]); }