预约号源调整

main
鹿和sa0ChunLuyu 6 days ago
parent 3544e0aecb
commit 7644ab0e4f

@ -348,6 +348,22 @@ public function CancelYuYue(Request $request)
} }
} }
//批量取消预约
public function BatchCancelYuYue(Request $request)
{
$ids = request('ids');
$reg_num = request('reg_num');
$userid = $request->get('userid');
$do_userid = request('do_user') ?: $userid;
if (empty($ids) || !is_array($ids)) {
return \Yz::echoError1('参数错误');
}
$service = new PlanListService();
return $service->BatchCancelYuYue($ids, $reg_num, $do_userid);
}
//查询已预约明细 //查询已预约明细
public function GetUsedList() public function GetUsedList()
{ {

@ -196,6 +196,17 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ
AND a.is_del = 0 AND a.is_del = 0
AND b.is_del = 0 AND b.is_del = 0
AND b.appointment_enabled = 1 AND b.appointment_enabled = 1
AND (
b.execute_department_id IS NULL
OR b.execute_department_id = 0
OR EXISTS (
SELECT 1 FROM s_department
WHERE id = b.execute_department_id
AND is_del = 0
AND appointment_enabled = 1
AND department_status = 1
)
)
AND c.appointment_type_id = ?", $canshu); AND c.appointment_type_id = ?", $canshu);
foreach ($plan as $p) { foreach ($plan as $p) {
@ -244,6 +255,17 @@ public function GetEnablePlan($regnum, $entrustids, $episodeid, $appointment_typ
AND a.is_del = 0 AND a.is_del = 0
AND b.is_del = 0 AND b.is_del = 0
AND b.appointment_enabled = 1 AND b.appointment_enabled = 1
AND (
b.execute_department_id IS NULL
OR b.execute_department_id = 0
OR EXISTS (
SELECT 1 FROM s_department
WHERE id = b.execute_department_id
AND is_del = 0
AND appointment_enabled = 1
AND department_status = 1
)
)
AND c.appointment_type_id IN ($appointment_types_placeholders)", $canshu); AND c.appointment_type_id IN ($appointment_types_placeholders)", $canshu);
$mergedPlan = []; $mergedPlan = [];
@ -548,36 +570,27 @@ public function GetOptimalPlan($type, $regnum, $entrustids, $episodeid, $appoint
} }
} }
// 最优时间分配:按科室分组,逐个 item 找最早可用号源,可跨天 // 最优时间分配:逐项目独立找最早可用号源,可跨天
private function doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids = [], $existingSharedAtPlan = []) private function doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids = [], $existingSharedAtPlan = [])
{ {
// 按科室分组
$deptGroups = $this->groupItemsByDept($items);
if (empty($deptGroups)) {
return \Yz::Return(true, '获取成功', []);
}
$result = []; $result = [];
$currentDate = clone $startDate; $usedCapacity = []; // 记录本次推荐中已分配占用的容量
// 每组待分配的 items $assignedPlans = []; // 记录已分配的时间段,用于冲突检查
$remainingGroups = [];
foreach ($deptGroups as $deptCode => $group) {
$remainingGroups[$deptCode] = [
'entrustids' => $group['entrustids'],
'items' => $group['items']
];
}
while ($currentDate <= $endDate && !empty($remainingGroups)) { // 逐项目独立分配
$nowdate = $currentDate->format('Y-m-d'); foreach ($items as $item) {
$newRemainingGroups = []; $entrustId = $item['entrust_id'] ?? null;
$globalAssignedPlans = []; // 跨科室组的全局已分配时间段 if (!$entrustId) {
$result[] = ['id' => $item['id'], 'plan_id' => 0, 'department_resources_name' => '', 'date' => '', 'begin_time' => '', 'end_time' => ''];
continue;
}
foreach ($remainingGroups as $deptCode => $group) { $assigned = false;
$groupEntrustids = $group['entrustids']; $currentDate = clone $startDate;
$groupItems = $group['items'];
$availablePlans = $this->getAvailablePlansForDate($regnum, $groupEntrustids, $episodeid, $appointment_type, $nowdate, $scope, $userDeptId); while ($currentDate <= $endDate) {
$nowdate = $currentDate->format('Y-m-d');
$availablePlans = $this->getAvailablePlansForDate($regnum, [$entrustId], $episodeid, $appointment_type, $nowdate, $scope, $userDeptId);
// 过滤已占用的号源 // 过滤已占用的号源
if (!empty($occupied_plan_ids)) { if (!empty($occupied_plan_ids)) {
@ -588,72 +601,52 @@ private function doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids,
} }
if (empty($availablePlans)) { if (empty($availablePlans)) {
// 当天该科室无号源,整组留到下一轮 $currentDate->modify('+1 day');
$newRemainingGroups[$deptCode] = $group;
continue; continue;
} }
$usedCapacity = []; // 找到第一个能容纳的号源
$assignedPlans = []; foreach ($availablePlans as $plan) {
$remainingItems = []; $planId = $plan->id;
$useSeats = $item['use_seats'] ?? 1;
foreach ($groupItems as $item) { if (!empty($item['is_share_slot']) && $useSeats > 0) {
$assigned = false; $existingMax = $existingSharedAtPlan[$planId] ?? 0;
foreach ($availablePlans as $plan) { $useSeats = max(0, $useSeats - $existingMax);
$planId = $plan->id;
$useSeats = $item['use_seats'] ?? 1;
// 共享项目号源持有者:减去已有共享项目的 max
if (!empty($item['is_share_slot']) && $useSeats > 0) {
$existingMax = $existingSharedAtPlan[$planId] ?? 0;
$useSeats = max(0, $useSeats - $existingMax);
}
$remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0);
$used = $usedCapacity[$planId] ?? 0;
if (($remaining - $used) < $useSeats) continue;
// 冲突检查:同一科室组内,不同排班时间不能重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue;
// 冲突检查:跨科室组,时间段不能重叠
if ($this->isTimeConflict($globalAssignedPlans, $plan)) continue;
$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) + $useSeats;
$assignedPlans[] = [
'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$globalAssignedPlans[] = [
'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$assigned = true;
break;
} }
if (!$assigned) {
$remainingItems[] = $item;
}
}
if (!empty($remainingItems)) { // 容量检查:减去数据库已用 + 本次已分配
$newRemainingGroups[$deptCode] = [ $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0);
'entrustids' => $groupEntrustids, $used = $usedCapacity[$planId] ?? 0;
'items' => $remainingItems if (($remaining - $used) < $useSeats) continue;
// 冲突检查:同一患者的不同项目时间段不能重叠
if ($this->isTimeConflict($assignedPlans, $plan)) continue;
$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) + $useSeats;
$assignedPlans[] = [
'resource_name' => $plan->department_resources_name,
'begin_time' => $plan->begin_time,
'end_time' => $plan->end_time
];
$assigned = true;
break;
} }
if ($assigned) break;
$currentDate->modify('+1 day');
} }
$remainingGroups = $newRemainingGroups; if (!$assigned) {
$currentDate->modify('+1 day'); $result[] = ['id' => $item['id'], 'plan_id' => 0, 'department_resources_name' => '', 'date' => '', 'begin_time' => '', 'end_time' => ''];
}
} }
return \Yz::Return(true, '获取成功', $result); return \Yz::Return(true, '获取成功', $result);
@ -1706,6 +1699,79 @@ public function CancelYuYue($MainListId, $reg_num,$do_user=null)
} }
} }
//批量取消预约:统一事务,避免并发号源校正冲突
public function BatchCancelYuYue($ids, $reg_num, $do_user = null)
{
date_default_timezone_set('PRC');
$nowdatetime = date('Y-m-d H:i:s');
DB::beginTransaction();
try {
$rosterIds = [];
foreach ($ids as $MainListId) {
$mainInfo = DB::table('s_list')
->where(['id' => $MainListId, 'reg_num' => $reg_num])
->lockForUpdate()
->first();
if (!$mainInfo) {
throw new \Exception("医嘱不存在: {$MainListId}");
}
if ($mainInfo->list_status != 1) {
throw new \Exception("{$mainInfo->entrust} 无法取消,当前状态:" . $mainInfo->list_status);
}
if ($mainInfo->roster_id) {
$rosterIds[] = $mainInfo->roster_id;
}
$u_data = [
'list_status' => 0,
'reservation_date' => null,
'reservation_time' => null,
'reservation_sources' => null,
'services_group' => null,
'roster_id' => null,
'xuhao' => null,
'department_id' => null,
'appointment_type_id' => null,
'appointment_use_plan_detail' => null,
'canel_time' => $nowdatetime,
'is_emergency' => 0,
];
DB::table('s_list')->where(['id' => $MainListId])->update($u_data);
DB::table('s_list_log')->insert([
'list_id' => $mainInfo->id,
'reg_num' => $mainInfo->reg_num,
'old_status' => $mainInfo->list_status,
'new_status' => 0,
'create_user' => $do_user,
'note' => '取消预约',
'data' => json_encode($u_data, JSON_UNESCAPED_UNICODE),
'created_at' => $nowdatetime,
]);
}
// 每个 roster 只校正一次
$rosterIds = array_unique($rosterIds);
foreach ($rosterIds as $rid) {
$this->correctUsedCount($rid);
}
DB::commit();
return \Yz::Return(true, '取消成功', []);
} catch (\Exception $e) {
DB::rollBack();
Log::error('批量取消预约失败', ['ids' => $ids, 'error' => $e->getMessage()]);
return \Yz::echoError1($e->getMessage());
}
}
public function releaseSourceFromDetail($mainListId, $mainInfo = null) public function releaseSourceFromDetail($mainListId, $mainInfo = null)
{ {
// 由 correctUsedCount 校正替代,不再在此处释放号源 // 由 correctUsedCount 校正替代,不再在此处释放号源
@ -1737,7 +1803,7 @@ private function releaseSourceFallback($mainInfo)
/** /**
* 校正排班号源根据项目配置is_share_slot / use_seats重新计算 used_count * 校正排班号源根据项目配置is_share_slot / use_seats重新计算 used_count
*/ */
private function correctUsedCount($rosterId) public function correctUsedCount($rosterId)
{ {
if (!$rosterId) return; if (!$rosterId) return;
@ -1748,9 +1814,10 @@ private function correctUsedCount($rosterId)
// 获取该排班上所有活跃项目及其配置 // 获取该排班上所有活跃项目及其配置
$activeItems = DB::table('s_list') $activeItems = DB::table('s_list')
->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name') ->join('s_check_item', 's_list.entrust_code', '=', 's_check_item.item_code')
->where('s_list.roster_id', $rosterId) ->where('s_list.roster_id', $rosterId)
->where('s_list.list_status', 1) ->whereIn('s_list.list_status', [1, 2, 3])
->where('s_list.is_nullify', 0)
->where('s_check_item.status', 1) ->where('s_check_item.status', 1)
->where('s_check_item.is_del', 0) ->where('s_check_item.is_del', 0)
->select('s_list.*', 's_check_item.use_seats', 's_check_item.is_share_slot') ->select('s_list.*', 's_check_item.use_seats', 's_check_item.is_share_slot')

@ -143,10 +143,41 @@ public function UpdateStatus($result, $jsonData)
if(!!$mainInfo){ if(!!$mainInfo){
if(in_array($order['OEORIStatusCode'],$zuofei)){ //如果是作废 if(in_array($order['OEORIStatusCode'],$zuofei)){ //如果是作废
$beizhu='作废'; $beizhu='作废';
$data=[ $nowdatetime = date('Y-m-d H:i:s');
'is_nullify'=>1,
'updated_at'=>date('Y-m-d H:i:s') if ($mainInfo->list_status != 0 && $mainInfo->roster_id) {
]; // 清空预约相关字段
$clearData = [
'list_status' => 0,
'reservation_date' => null,
'reservation_time' => null,
'reservation_sources' => null,
'services_group' => null,
'roster_id' => null,
'xuhao' => null,
'department_id' => null,
'appointment_type_id' => null,
'appointment_use_plan_detail' => null,
'canel_time' => $nowdatetime,
'is_emergency' => 0,
'is_nullify' => 1,
'updated_at' => $nowdatetime,
];
$update = DB::table('s_list')->where('id', $mainInfo->id)->update($clearData);
// 校正号源
if ($update) {
$planService = new PlanListService();
$planService->correctUsedCount($mainInfo->roster_id);
}
} else {
$data = [
'is_nullify' => 1,
'updated_at' => $nowdatetime,
];
$update = DB::table('s_list')->where('id', $mainInfo->id)->update($data);
}
} }
if(in_array($order['OEORIStatusCode'],$jiaofei)){ //如果是完成 if(in_array($order['OEORIStatusCode'],$jiaofei)){ //如果是完成
$beizhu='缴费完成'; $beizhu='缴费完成';

@ -181,6 +181,7 @@
Route::post('admin/GetPlanUsedList','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetUsedList');//计划占用详情列表 Route::post('admin/GetPlanUsedList','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetUsedList');//计划占用详情列表
Route::get('admin/NoPayCancel','App\Http\Controllers\API\Admin\YeWu\WorkMainController@NoPayCancel');//检查是否有超时未支付的门诊预约记录,如果有则给其取消,并恢复名额 Route::get('admin/NoPayCancel','App\Http\Controllers\API\Admin\YeWu\WorkMainController@NoPayCancel');//检查是否有超时未支付的门诊预约记录,如果有则给其取消,并恢复名额
Route::post('admin/DoctorCancelYuYue','App\Http\Controllers\API\Admin\YeWu\WorkMainController@DoctorCancelYuYue');//医生取消预约 Route::post('admin/DoctorCancelYuYue','App\Http\Controllers\API\Admin\YeWu\WorkMainController@DoctorCancelYuYue');//医生取消预约
Route::post('admin/BatchCancelYuYue','App\Http\Controllers\API\Admin\YeWu\PlanListController@BatchCancelYuYue');//批量取消预约
Route::post('admin/NearestEnablePlanDate','App\Http\Controllers\API\Admin\YeWu\PlanListController@NearestEnablePlanDate');//获取最近可用日期 Route::post('admin/NearestEnablePlanDate','App\Http\Controllers\API\Admin\YeWu\PlanListController@NearestEnablePlanDate');//获取最近可用日期
Route::post('testSendMsg','App\Http\Controllers\TestSendMsgController@SendMsg');//测试发送短信 Route::post('testSendMsg','App\Http\Controllers\TestSendMsgController@SendMsg');//测试发送短信
Route::post('admin/GetPersonYuYueList','App\Http\Controllers\API\Admin\YeWu\WorkMainController@GetPersonYuYueList');//医生获取某人全部预约记录 Route::post('admin/GetPersonYuYueList','App\Http\Controllers\API\Admin\YeWu\WorkMainController@GetPersonYuYueList');//医生获取某人全部预约记录

@ -582,6 +582,10 @@ export const CheckIsDaiJian = (data = {}) => {
export const DoctorCancelYuYue = (data = {}) => { export const DoctorCancelYuYue = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'admin/DoctorCancelYuYue', data: data }) return axios({ url: import.meta.env.VITE_APP_API + 'admin/DoctorCancelYuYue', data: data })
} }
//批量取消预约
export const BatchCancelYuYue = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'admin/BatchCancelYuYue', data: data })
}
//预约渠道统计 //预约渠道统计
export const countAppointmentType = (data = {}) => { export const countAppointmentType = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/countAppointmentType', data: data }) return axios({ url: import.meta.env.VITE_APP_API + 'v1/admin/countAppointmentType', data: data })

@ -224,7 +224,8 @@
CheckIsDaiJian, CheckIsDaiJian,
PlanYuYue, PlanYuYue,
CheckEntrstItemGroup2, CheckEntrstItemGroup2,
DoctorCancelYuYue,GetConfigInfo2,GetOptimalPlan DoctorCancelYuYue,GetConfigInfo2,GetOptimalPlan,
BatchCancelYuYue
} from '@/api/api.js' } from '@/api/api.js'
import { import {
ElMessage, ElMessage,
@ -651,8 +652,10 @@
cachedPlans.value = plans cachedPlans.value = plans
zhanWeiCount.value = zwCount zhanWeiCount.value = zwCount
if (plans.length > 0) { if (plans.length > 0) {
const deptExists = plans.some(v => v.department_resources_name === activeZhenShi.value)
if (activeZhenShi.value == '') activeZhenShi.value = plans[0].department_resources_name if (!deptExists) {
activeZhenShi.value = plans[0].department_resources_name
}
const timeSlotsSet = new Set(); const timeSlotsSet = new Set();
const zhenshiSet = new Set(); const zhenshiSet = new Set();
plans.forEach(v => { plans.forEach(v => {
@ -1708,32 +1711,19 @@
})) }))
loading.value = true loading.value = true
const promises = selectedRows.value.map((v) => { const res = await BatchCancelYuYue({
return DoctorCancelYuYue({ ids: selectedRows.value.map(v => v.id),
MainListId: v.id, reg_num: selectedRows.value[0].reg_num,
reg_num: v.reg_num, do_user: props.do_user,
do_user:props.do_user,
}).then(res => {
if (res.status) {
ElMessage.success('取消成功')
} else {
ElMessage.error(res.msg)
}
}).catch(err => {
ElMessage.error('取消失败')
console.error(err)
})
}) })
// if (res.status) {
await Promise.all(promises) ElMessage.success('取消成功')
loading.value = false
loading.value = false GetMainInfo(() => {
GetMainInfo(() => { entrustTableDate.value.forEach(v => {
// if (tempPlanCache[v.id]) {
entrustTableDate.value.forEach(v => { v.temp_plan_id = tempPlanCache[v.id].temp_plan_id
if (tempPlanCache[v.id]) {
v.temp_plan_id = tempPlanCache[v.id].temp_plan_id
if (!v.department_resources) v.department_resources = {} if (!v.department_resources) v.department_resources = {}
v.department_resources.department_resources_name = tempPlanCache[v.id].department_resources_name v.department_resources.department_resources_name = tempPlanCache[v.id].department_resources_name
v.reservation_date = tempPlanCache[v.id].reservation_date v.reservation_date = tempPlanCache[v.id].reservation_date
@ -1782,6 +1772,10 @@
}) })
} }
}, true) }, true)
} else {
ElMessage.error(res.msg || '取消失败')
loading.value = false
}
}) })

Loading…
Cancel
Save