From 3d22681335940153307000a21e17dfc5b67246d4 Mon Sep 17 00:00:00 2001 From: sa0ChunLuyu Date: Fri, 24 Jul 2026 13:30:04 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../API/Admin/YeWu/PlanListController.php | 37 ++- .../API/Admin/YeWu/WorkMainController.php | 7 +- .../Services/Admin/YeWu/PlanListService.php | 156 +++++++++++- Laravel/routes/api.php | 1 + YiJi-admin/src/api/api.js | 7 + .../src/components/Yewu/YuYue202506.vue | 222 ++++++++++++++++-- YiJi-admin/src/views/YeWu/MainList.vue | 2 +- 7 files changed, 407 insertions(+), 25 deletions(-) diff --git a/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php b/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php index 4733a45..aa0b0db 100644 --- a/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php +++ b/Laravel/app/Http/Controllers/API/Admin/YeWu/PlanListController.php @@ -200,8 +200,43 @@ public function GetEnablePlan() $episodeid = request('episodeid'); $appointment_type = request('appointment_type'); //预约类型 $appointment_date = request('date'); //预约日期 + $scope = request('scope', 'all'); + + $userDeptId = 0; + if ($scope === 'department') { + $userid = request('do_user'); + if ($userid) { + $userInfo = DB::table('users')->where(['id' => $userid])->first(); + $userDeptId = $userInfo->department_id ?? 0; + } + } + + $service = new PlanListService(); + return $service->GetEnablePlan($regnum, $entrustid, $episodeid, $appointment_type, $appointment_date, $scope, $userDeptId); + } + + //最优时间/单天全检 分配 + public function GetOptimalPlan() + { + $type = request('type'); // optimal_time | full_day + $regnum = request('regnum'); + $entrustid = request('entrustid'); + $episodeid = request('episodeid'); + $appointment_type = request('appointment_type'); + $items = request('items'); + $scope = request('scope', 'all'); + + $userDeptId = 0; + if ($scope === 'department') { + $userid = request('do_user'); + if ($userid) { + $userInfo = DB::table('users')->where(['id' => $userid])->first(); + $userDeptId = $userInfo->department_id ?? 0; + } + } + $service = new PlanListService(); - return $service->GetEnablePlan($regnum, $entrustid, $episodeid, $appointment_type, $appointment_date); + return $service->GetOptimalPlan($type, $regnum, $entrustid, $episodeid, $appointment_type, $items, $scope, $userDeptId); } //获取最近可用的,计划 日期 diff --git a/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php b/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php index 24c6796..2cfa400 100644 --- a/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php +++ b/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php @@ -145,13 +145,16 @@ public function GetList(Request $request) }) ->pluck('department_number'); - //工作台科室:合并关联科室的 department_number + //工作台科室:合并关联科室及子科室的 department_number if ($department->is_workbench == 1) { $linkedIds = json_decode($department->linked_departments, true); if (!empty($linkedIds) && is_array($linkedIds)) { $linkedDeptNumbers = DB::table('s_department') - ->whereIn('id', $linkedIds) ->where('is_del', 0) + ->where(function($q) use ($linkedIds) { + $q->whereIn('id', $linkedIds) + ->orWhereIn('pid', $linkedIds); + }) ->pluck('department_number'); $deptNumbers = $deptNumbers->merge($linkedDeptNumbers); } diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index 8c27f42..a7999a9 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -14,7 +14,7 @@ class PlanListService { - public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_type, $appointment_date) + public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_type, $appointment_date, $scope = 'all', $userDeptId = 0) { date_default_timezone_set('PRC'); @@ -93,6 +93,41 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ } } + // scope=department 时,使用用户科室过滤号源(含工作台+子科室) + $deptIds = []; + $useScopeDept = false; + if ($scope === 'department' && $userDeptId > 0) { + $useScopeDept = true; + $userDept = DB::table('s_department')->where(['id' => $userDeptId, 'is_del' => 0])->first(); + if ($userDept) { + // 当前科室 + 子科室 + $deptIds = DB::table('s_department') + ->where('is_del', 0) + ->where(function($q) use ($userDeptId) { + $q->where('id', $userDeptId) + ->orWhere('pid', $userDeptId); + }) + ->pluck('id') + ->toArray(); + + // 工作台科室:合并关联科室及子科室 + if ($userDept->is_workbench == 1) { + $linkedIds = json_decode($userDept->linked_departments, true); + if (!empty($linkedIds) && is_array($linkedIds)) { + $linkedDeptIds = DB::table('s_department') + ->where('is_del', 0) + ->where(function($q) use ($linkedIds) { + $q->whereIn('id', $linkedIds) + ->orWhereIn('pid', $linkedIds); + }) + ->pluck('id') + ->toArray(); + $deptIds = array_unique(array_merge($deptIds, $linkedDeptIds)); + } + } + } + } + if(count($commonDevice)==0) return \Yz::echoError1("无可用号源"); $placeholders = implode(',', array_fill(0, count($commonDevice), '?')); $appointment_types_placeholders = implode(',', array_fill(0, count($appointment_types), '?')); @@ -109,7 +144,14 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ if ($isDeviceMode) { //设备模式:不限制科室,只按单渠道查号源,渠道合路后置处理 - $canshu = array_merge($commonDevice, $appointment_date_arr, [$appointment_type]); + if ($useScopeDept && !empty($deptIds)) { + $deptPlaceholders = implode(',', array_fill(0, count($deptIds), '?')); + $canshu = array_merge($commonDevice, $appointment_date_arr, $deptIds, [$appointment_type]); + $deptCondition = "AND a.department_id IN ($deptPlaceholders)"; + } else { + $canshu = array_merge($commonDevice, $appointment_date_arr, [$appointment_type]); + $deptCondition = ''; + } $plan = DB::select("SELECT a.*, dd.devices, @@ -136,6 +178,7 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ ) AS dd ON a.id = dd.roster_detail_id WHERE a.date IN ($appointment_dates_placeholders) + $deptCondition AND a.STATUS = 1 AND a.is_del = 0 AND b.is_del = 0 @@ -149,7 +192,14 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ $plan = $this->mergeChannelsPerDepartment($plan, $appointment_type); } else { //科室模式:原有逻辑 - $canshu = array_merge($commonDevice, [$department_id->id], $appointment_date_arr, $appointment_types); + if ($useScopeDept) { + $deptPlaceholders = implode(',', array_fill(0, count($deptIds), '?')); + $canshu = array_merge($commonDevice, $deptIds, $appointment_date_arr, $appointment_types); + $deptCondition = "a.department_id IN ($deptPlaceholders)"; + } else { + $canshu = array_merge($commonDevice, [$department_id->id], $appointment_date_arr, $appointment_types); + $deptCondition = "a.department_id = ?"; + } $plan = DB::select("SELECT a.*, dd.devices, @@ -175,7 +225,7 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ d.roster_detail_id ) AS dd ON a.id = dd.roster_detail_id WHERE - a.department_id = ? + $deptCondition AND a.date IN ($appointment_dates_placeholders) AND a.STATUS = 1 AND a.is_del = 0 @@ -372,6 +422,104 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ ]); } + //最优时间/单天全检 分配 + public function GetOptimalPlan($type, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope = 'all', $userDeptId = 0) + { + date_default_timezone_set('PRC'); + $dateRange = config('app.globals.可用号源查询范围'); + $result = []; + $usedCapacity = []; // 记录已分配的号源容量,key: roster_detail_id, value: 已分配数 + + // 遍历日期范围 + $startDate = new DateTime(); + $endDate = new DateTime(); + $endDate->modify('+' . $dateRange . ' day'); + $currentDate = clone $startDate; + + while ($currentDate <= $endDate) { + $nowdate = $currentDate->format('Y-m-d'); + + // 获取当前日期的可用号源 + $s = $this->GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_type, $nowdate, $scope, $userDeptId); + + if (!$s['status']) { + $currentDate->modify('+1 day'); + continue; + } + + $planList = $s['data']['plan_list'] ?? []; + if (count($planList) == 0) { + $currentDate->modify('+1 day'); + continue; + } + + // 筛选未过期的、可用的号源 + $nowtime = date('Y-m-d H:i:s'); + $availablePlans = []; + foreach ($planList as $plan) { + $planEndTime = $plan->date . ' ' . $plan->end_time; + if ($planEndTime > $nowtime && $plan->plan_enable) { + $availablePlans[] = $plan; + } + } + + if (count($availablePlans) == 0) { + $currentDate->modify('+1 day'); + continue; + } + + // 按日期、时间排序 + usort($availablePlans, function($a, $b) { + $timeA = $a->date . ' ' . ($a->begin_time ?? '00:00:00'); + $timeB = $b->date . ' ' . ($b->begin_time ?? '00:00:00'); + return $timeA <=> $timeB; + }); + + // 遍历 items,逐个分配 + foreach ($items as $item) { + // 如果该 item 已经分配,跳过 + $alreadyAssigned = false; + foreach ($result as $r) { + if ($r['id'] == $item['id']) { + $alreadyAssigned = true; + break; + } + } + if ($alreadyAssigned) continue; + + // 找可用号源 + foreach ($availablePlans as $plan) { + $planId = $plan->id; + $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0) - ($plan->locked_count ?? 0); + $used = $usedCapacity[$planId] ?? 0; + + if (($remaining - $used) > 0) { + // 分配此号源 + $result[] = [ + 'id' => $item['id'], + 'plan_id' => $planId, + 'department_resources_name' => $plan->department_resources_name, + 'date' => $plan->date, + 'begin_time' => $plan->begin_time, + 'end_time' => $plan->end_time + ]; + $usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1; + break; + } + } + } + + // 如果所有 items 都已分配,停止遍历 + if (count($result) >= count($items)) { + break; + } + + $currentDate->modify('+1 day'); + } + + return \Yz::Return(true, '获取成功', $result); + } + //开始预约占用名额 public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_emergency=0,$do_user=null) { diff --git a/Laravel/routes/api.php b/Laravel/routes/api.php index efd8360..fbc22e5 100644 --- a/Laravel/routes/api.php +++ b/Laravel/routes/api.php @@ -151,6 +151,7 @@ Route::post('admin/GetConfigInfo','App\Http\Controllers\API\Admin\ConfigController@GetConfigInfo2'); //获取配置信息 Route::post('admin/getMainDetail','App\Http\Controllers\API\Admin\YeWu\WorkMainController@getMainDetail');//获取主表信息 Route::post('admin/GetEnablePlan','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetEnablePlan');//获取可用的计划,用于计划占用 + Route::post('admin/GetOptimalPlan','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetOptimalPlan');//最优时间/单天全检 分配 Route::post('admin/PlanYuYue','App\Http\Controllers\API\Admin\YeWu\PlanListController@YuYue');//开始预约 Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约 Route::post('admin/CheckIsDaiJian','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckIsDaiJian');//检查当前时段是否有存在已经预约的待检查项目 diff --git a/YiJi-admin/src/api/api.js b/YiJi-admin/src/api/api.js index f28b90f..fcbcc1e 100644 --- a/YiJi-admin/src/api/api.js +++ b/YiJi-admin/src/api/api.js @@ -469,6 +469,13 @@ export const GetEnablePlan = (data = {}) => { return axios({ url: import.meta.env.VITE_APP_API + 'admin/GetEnablePlan', data: data }) } //获取某日可用号源 +//最优时间/单天全检 分配 +// type: optimal_time | full_day +// items: 所有申请中行的当前排班信息 +export const GetOptimalPlan = (data = {}) => { + return axios({ url: import.meta.env.VITE_APP_API + 'admin/GetOptimalPlan', data: data }) +} +//获取某日可用号源 export const PlanYuYue = (data = {}) => { return axios({ url: import.meta.env.VITE_APP_API + 'admin/PlanYuYue', data: data }) } diff --git a/YiJi-admin/src/components/Yewu/YuYue202506.vue b/YiJi-admin/src/components/Yewu/YuYue202506.vue index 89b8d13..a0ebf88 100644 --- a/YiJi-admin/src/components/Yewu/YuYue202506.vue +++ b/YiJi-admin/src/components/Yewu/YuYue202506.vue @@ -84,8 +84,8 @@ 预约完成后打印申请单 - 最优时间 - 单天全检 + 最优时间 + 单天全检
@@ -157,7 +157,7 @@ CheckIsDaiJian, PlanYuYue, CheckEntrstItemGroup2, - DoctorCancelYuYue,GetConfigInfo2,CreateJianChaShenQingDanPdf + DoctorCancelYuYue,GetConfigInfo2,CreateJianChaShenQingDanPdf,GetOptimalPlan } from '@/api/api.js' import { ElMessage, @@ -187,6 +187,10 @@ special_privileges:{ type: Array, default: () => [] + }, + scope: { + type: String, + default: 'all' } }) let entrustTableRef = ref(null) @@ -210,6 +214,10 @@ let handleType = ref('add'); let startDate = ref(''); let systemConfigs=ref([]);//系统参数 + let cachedListData = ref([]) // 缓存列表原始数据,每次GetMainInfo更新 + let cachedPlans = ref([]) // 缓存号源原始数据,每次GetEnablePlanFunc更新 + let zhanWeiCount = ref(0) // 当前选中项目的号位需求数 + let hasAutoAssignedFirstPlan = ref(false) // 首次自动分配标志 let TanChuangMsgDialogVisible=ref(false) const getWeekday = (date1) => { let days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; @@ -383,6 +391,8 @@ } } + // 缓存列表原始数据(深拷贝,与显示列表解耦) + cachedListData.value = JSON.parse(JSON.stringify(entrustTableDate.value)) } } else { ElMessage.error(res.msg) @@ -463,15 +473,20 @@ entrustid: selectedEntrustId.value, episodeid: props.episode_id, appointment_type: props.appointment_type, - date: date_list.value + date: date_list.value, + scope: props.scope, + do_user: props.do_user }).then(res => { planLoading.value = false planTableData.value = [] zhenShiList.value=[] if (res.status) { let plans = res.data.plan_list - let zhanWeiCount=res.data.zhanWeiCount + let zwCount=res.data.zhanWeiCount let is_emergency=res.data.is_emergency + // 保存号源数据到顶层变量,供PlanClick使用 + cachedPlans.value = plans + zhanWeiCount.value = zwCount if (plans.length > 0) { if (activeZhenShi.value == '') activeZhenShi.value = plans[0].department_resources_name @@ -506,7 +521,7 @@ ); let plan_enable = false if (matchingPlan) { - if (matchingPlan.count - matchingPlan.used_count >= zhanWeiCount && matchingPlan.plan_enable===true) { + if (matchingPlan.count - matchingPlan.used_count >= zwCount && matchingPlan.plan_enable===true) { plan_enable = true } if(is_emergency===true && matchingPlan.plan_enable===true && canEmergency.value==true){ @@ -529,15 +544,67 @@ planTableData.value.push(row); }); - // 默认选中第一个有号源的 plan(按后端排序:时间最近、号源最多) + // 优先高亮已分配或已预约的号源 selectedPlanId.value = 0 - const firstAvailable = plans.find(v => - v.department_resources_name == activeZhenShi.value && - (v.count - v.used_count) >= zhanWeiCount && - v.plan_enable === true - ); - if (firstAvailable) { - selectedPlanId.value = firstAvailable.id + const highlightPlanId = findHighlightPlanId() + if (highlightPlanId) { + selectedPlanId.value = highlightPlanId + } else { + // 没有匹配的,才高亮第一个可用号源 + const firstAvailable = plans.find(v => + v.department_resources_name == activeZhenShi.value && + (v.count - v.used_count) >= zwCount && + v.plan_enable === true + ); + if (firstAvailable) { + selectedPlanId.value = firstAvailable.id + } + } + + // 首次加载时自动分配最优时间 + if (!hasAutoAssignedFirstPlan.value) { + hasAutoAssignedFirstPlan.value = true + const unbookedRows = entrustTableDate.value.filter(v => v.list_status === 0) + if (unbookedRows.length > 0) { + const items = unbookedRows.map(v => ({ + id: v.id, + entrust_id: v.entrust_id, + department_resources_name: v.department_resources?.department_resources_name || '', + date: v.reservation_date || '', + begin_time: v.period_begin_time || '', + end_time: v.period_end_time || '' + })) + GetOptimalPlan({ + type: 'optimal_time', + regnum: props.reg_num, + entrustid: selectedEntrustId.value, + episodeid: props.episode_id, + appointment_type: props.appointment_type, + items: items, + scope: props.scope, + do_user: props.do_user + }).then(res => { + if (res.status) { + res.data.forEach(item => { + const target = entrustTableDate.value.find(v => v.id === item.id) + if (!target) return + if (!target.department_resources) { + target.department_resources = {} + } + target.department_resources.department_resources_name = item.department_resources_name + target.reservation_date = item.date + target.period_begin_time = item.begin_time + target.period_end_time = item.end_time + target.temp_plan_id = item.plan_id + }) + // 更新高亮 + const newHighlightId = findHighlightPlanId() + if (newHighlightId) { + selectedPlanId.value = newHighlightId + } + } + }) + } } } @@ -585,13 +652,134 @@ selectedPlanId.value = 0 GetEnablePlanFunc() } + // 从已分配或已预约的行中,找到在当前排班下可高亮的号源ID + const findHighlightPlanId = () => { + // 优先找申请中行的临时分配号源 + for (const row of entrustTableDate.value) { + if (row.list_status === 0 && row.temp_plan_id) { + const plan = cachedPlans.value.find(v => + v.id === row.temp_plan_id && + v.department_resources_name === activeZhenShi.value + ) + if (plan) return plan.id + } + } + // 再找已预约行的roster_id + for (const row of entrustTableDate.value) { + if (row.list_status === 1 && row.roster_id) { + const plan = cachedPlans.value.find(v => + v.id === row.roster_id && + v.department_resources_name === activeZhenShi.value + ) + if (plan) return plan.id + } + } + return 0 + } const PlanClick = (planid) => { console.log(planid) - if (planid != undefined) { - selectedPlanId.value = planid - checkDaijianFuc() + if (planid == undefined) return + + // 从缓存中找到对应号源数据 + const plan = cachedPlans.value.find(v => v.id === planid) + if (!plan) return + + // 判断剩余号源是否足够 + const remaining = plan.count - plan.used_count + if (remaining < zhanWeiCount.value) { + ElMessage.warning(`剩余号源不足,当前仅剩 ${remaining} 个号位,所选项目需 ${zhanWeiCount.value} 个号位`) + return } + selectedPlanId.value = planid + checkDaijianFuc() + + // 同步更新显示列表中的排班、预约日期、预约时间 + selectedRows.value.forEach(row => { + const target = entrustTableDate.value.find(v => v.id === row.id) + if (!target) return + // 排班 + if (!target.department_resources) { + target.department_resources = {} + } + target.department_resources.department_resources_name = plan.department_resources_name + // 预约日期 + target.reservation_date = plan.date + // 预约时间 + target.period_begin_time = plan.begin_time + target.period_end_time = plan.end_time + // 保存临时分配的号源ID + target.temp_plan_id = planid + }) + + } + // 最优时间按钮点击 + const optimalTimeClick = () => { + if (selectedEntrustId.value.length === 0) { + ElMessage.error("请选择检查项目") + return + } + planLoading.value = true + + // 清空当前勾选行的临时排班信息(从缓存恢复原始数据) + selectedRows.value.forEach(row => { + const cachedRow = cachedListData.value.find(v => v.id === row.id) + if (!cachedRow) return + const target = entrustTableDate.value.find(v => v.id === row.id) + if (!target) return + target.department_resources = cachedRow.department_resources + target.reservation_date = cachedRow.reservation_date + target.period_begin_time = cachedRow.period_begin_time + target.period_end_time = cachedRow.period_end_time + target.temp_plan_id = undefined + }) + + // 收集所有申请中行的当前信息 + const items = entrustTableDate.value + .filter(v => v.list_status === 0) + .map(v => ({ + id: v.id, + entrust_id: v.entrust_id, + department_resources_name: v.department_resources?.department_resources_name || '', + date: v.reservation_date || '', + begin_time: v.period_begin_time || '', + end_time: v.period_end_time || '' + })) + + // 调用后端接口 + GetOptimalPlan({ + type: 'optimal_time', + regnum: props.reg_num, + entrustid: selectedEntrustId.value, + episodeid: props.episode_id, + appointment_type: props.appointment_type, + items: items, + scope: props.scope, + do_user: props.do_user + }).then(res => { + planLoading.value = false + if (res.status) { + // 更新显示列表 + res.data.forEach(item => { + const target = entrustTableDate.value.find(v => v.id === item.id) + if (!target) return + if (!target.department_resources) { + target.department_resources = {} + } + target.department_resources.department_resources_name = item.department_resources_name + target.reservation_date = item.date + target.period_begin_time = item.begin_time + target.period_end_time = item.end_time + target.temp_plan_id = item.plan_id + }) + // 更新高亮 + if (res.data.length > 0) { + selectedPlanId.value = res.data[0].plan_id + } + } else { + ElMessage.error(res.msg) + } + }) } const checkDaijianFuc = () => { planLoading.value = true diff --git a/YiJi-admin/src/views/YeWu/MainList.vue b/YiJi-admin/src/views/YeWu/MainList.vue index 419b2f0..b8f5e62 100644 --- a/YiJi-admin/src/views/YeWu/MainList.vue +++ b/YiJi-admin/src/views/YeWu/MainList.vue @@ -4,7 +4,7 @@ - +