预约执行科室不一致的提示

dev
鹿和sa0ChunLuyu 8 hours ago
parent 299908f6b4
commit c4c01f7b08

@ -303,6 +303,20 @@ public function YuYue()
$service = new PlanListService(); $service = new PlanListService();
return $service->YuYue($planid, $appointment_type, $mainlistid, $do_type,$is_emergency,$do_userid); return $service->YuYue($planid, $appointment_type, $mainlistid, $do_type,$is_emergency,$do_userid);
} }
public function PreCheckYuYue()
{
$do_userid = request('do_user') ?: request()->get('userid');
$is_emergency = request('is_emergency');
$appointment_type = request('appointment_type');
$do_type = request('dotype');
$groups = request('groups');
if (!isset($do_type)) return \Yz::echoError1('参数:操作类型 不能为空');
if (empty($groups) || !is_array($groups)) return \Yz::echoError1('参数groups 不能为空');
$service = new PlanListService();
return $service->PreCheckYuYue($groups, $appointment_type, $do_type, $is_emergency, $do_userid);
}
//自动预约 //自动预约
public function AutoYuYue() public function AutoYuYue()
{ {

@ -1074,231 +1074,28 @@ private function groupItemsByDept($items)
//开始预约占用名额 //开始预约占用名额
public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_emergency=0,$do_user=null) public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_emergency=0,$do_user=null)
{ {
date_default_timezone_set('PRC'); // 使用公共方法进行前置条件校验(含共享号源预计算、批次内互斥校验)
$nowdatetime = date("Y-m-d H:i:s"); $validation = $this->validateYuYueConditions($planid, $mainlistids, $do_type, $is_emergency, $do_user);
if (!$validation['pass']) {
if ($is_emergency == 1 && !empty($do_user)) {//如果是加急,校验用户是否有加急权限 return \Yz::echoError1($validation['fail_items'][0]['msg']);
$do_user_info=DB::table('users')->where('id',$do_user)->first(); }
if(!$do_user_info){
return \Yz::echoError1('无效的用户ID无法进行加急预约'); $planInfo = $validation['planInfo'];
}else{ $is_emergency = $validation['is_emergency'];
$special_privileges=[]; $resourceDeviceIds = $validation['resourceDeviceIds'];
$decoded=json_decode($do_user_info->special_privileges,true); $oldMainInfos = $validation['oldMainInfos'];
if(is_array($decoded)){ $itemSeatsData = $validation['itemSeatsData'];
$special_privileges=$decoded; $huchiList = $validation['huchiList'];
} $shareSlotIds = $validation['shareSlotIds'];
if(!in_array('emergency', $special_privileges)){ $shareSlotMax = $validation['shareSlotMax'];
return \Yz::echoError1('无权进行加急预约'); $maxShareSlotId = $validation['maxShareSlotId'];
}
} // 构建 itemSeatsQueue兼容原有流式分配逻辑
}else{ $itemSeatsQueue = [];
$is_emergency=0;
}
$planInfo = DB::table('s_source_roster_detail')->where(['id' => $planid, 'status' => 1, 'is_del' => 0])->first();
if (!$planInfo) return \Yz::echoError1('当前时段不可用');
if ($nowdatetime > $planInfo->date . ' ' . $planInfo->end_reservation_time) return \Yz::echoError1('已经超过预约截止时间');
// 查询当前排班资源绑定的设备ID列表用于校验检查项目与资源的匹配性
$resourceDeviceIds = DB::table('s_department_resources_device')
->where('rsourece_id', $planInfo->resources_id)
->pluck('device_id')
->toArray();
$zhanweiCount=0;//各个检查项目需要占用检查名额的总和
$itemSeatsQueue = []; // 队列:按顺序存储每个项目需要的座位数 [项目ID => 座位数]
$itemSeatsData = []; // 原始数据临时收集每个项目的use_seats和is_share_slot
$huchiList=[];//互斥item_id对应关系
$oldMainInfos = [];//临时存储原来的主表信息,用于改约
//遍历多个s_list表id,前端多选,一次预约多个检查项目
foreach ($mainlistids as $key_m => $mainlistid) {
$mainInfo = DB::table('s_list as a')
->select('a.*', DB::raw('COALESCE(a.reservation_begin_time, b.period_begin_time) as period_begin_time'), DB::raw('COALESCE(a.reservation_end_time, b.period_end_time) as period_end_time'))
->leftJoin('s_period as b', 'a.reservation_time', '=', 'b.id')
->where(['a.id' => $mainlistid])->first();
$oldMainInfos[] = $mainInfo;
if (!$mainInfo) return \Yz::echoError1('医嘱不存在');
//判断状态
$msg_t = "";
if ($mainInfo->list_status == 0) {
$msg_t = "当前状态为待预约";
}
if ($mainInfo->list_status == 1) {
$msg_t = "已经在" . $mainInfo->reservation_date . '的' . $mainInfo->period_begin_time . '-' . $mainInfo->period_end_time . '预约成功,不能再次预约';
}
if ($mainInfo->list_status == 2) {
$msg_t = "当前状态为已登记";
}
if ($mainInfo->list_status == 3) {
$msg_t = "当前状态为已完成";
}
if ($do_type == 1 && $mainInfo->list_status <> 0) return \Yz::echoError1($mainInfo->entrust . ' ' . $msg_t . ',禁止预约');
if ($do_type == 2 && $mainInfo->list_status <> 1) return \Yz::echoError1($mainInfo->entrust . ' ' . $msg_t . ',不允许改约操作');
if ($do_type == 2 && $mainInfo->roster_id == $planid) return \Yz::echoError1($mainInfo->entrust . ' ' . ',改约时间与之前相同,无需改约');
//判断病人类型
$plan_patient_type=explode(",", $planInfo->patient_type);
if(!in_array($mainInfo->patient_type, $plan_patient_type)) return \Yz::echoError1('当前计划不支持此病人类型');
//判断互斥暂时根据reg_num判断身份
//查询想要预约的项目 其自身code
$deptId = $planInfo->department_id ?? 0;
$item = DB::table('s_check_item')
->leftJoin('s_dept_check_item', function($j) use ($deptId) {
$j->on('s_dept_check_item.check_item_id', '=', 's_check_item.id')
->where('s_dept_check_item.department_id', '=', $deptId);
})
->where(['s_check_item.item_code' => $mainInfo->entrust_code, 's_check_item.status' => 1, 's_check_item.is_del' => 0])
->select('s_check_item.*',
DB::raw('COALESCE(s_dept_check_item.use_seats, s_check_item.use_seats, 1) as use_seats'),
DB::raw('COALESCE(s_dept_check_item.is_share_slot, s_check_item.is_share_slot, 0) as is_share_slot'),
DB::raw('COALESCE(s_dept_check_item.check_begin_time, s_check_item.check_begin_time, 0) as check_begin_time'),
DB::raw('COALESCE(s_dept_check_item.limosis, s_check_item.limosis) as limosis'))
->first();
if (!$item) return \Yz::echoError1('此检查项目不可用');
// 校验检查项目与排班资源的匹配性(通过设备/服务组交集判断)
$itemDeviceIds = DB::table('s_check_item_device')
->where('item_id', $item->id)
->pluck('device_id')
->toArray();
if (empty(array_intersect($itemDeviceIds, $resourceDeviceIds))) {
return \Yz::echoError1($item->item_name . ' 不支持在当前检查资源上预约');
}
$itemSeatsData[$mainlistid] = [
'use_seats' => $item->use_seats,
'is_share_slot' => $item->is_share_slot ?? 0,
];
//医嘱开具后,预约时间需在设定的等待期之后,单位分钟
$entrust_time = $mainInfo->entrust_date . ' ' . $mainInfo->entrust_time; //医嘱时间
$date = new DateTime($entrust_time);
$date->modify("+" . $item->check_begin_time . " minutes");
$enableCheckTime = $date;//到此时间后可进行预约
$plan_dateTime = $planInfo->date . ' ' . $planInfo->end_reservation_time;
$plan_dateTime = new DateTime($plan_dateTime);
if ($plan_dateTime < $enableCheckTime and $item->check_begin_time>0) return \Yz::echoError1($item->item_name . " 已设置只能预约医嘱开具后" . $item->check_begin_time . "分钟后的时间,请预约" . $enableCheckTime->format("Y-m-d H:i:s") . "之后的时间");
//检测是否空腹
$configs = DB::table('configs')->where('label', '开启空腹')->first();
if ($item->limosis == 1 and $planInfo->end_reservation_time > '12:00:00' and $configs->value == 1) return \Yz::echoError1($item->item_name . ' 项目必须空腹,只能预约上午,请重新选择时间');
//查询当前检查项目是否存在互斥
$cha_hc = DB::table('s_huchi')->where('is_del', 0)
->where(function ($q) use ($item) {
$q->where(['code1' => $item->item_code])->orWhere('code2', $item->item_code);
})->get();
foreach ($cha_hc as $hc) {
$huchiList[]=[$hc->code1,$hc->code2];
}
if (count($cha_hc) > 0) {
//查询用户预约中的医嘱
$status_1 = DB::table('s_list')
->select('s_check_item.item_code', 's_list.entrust', 's_list.reservation_date', 's_list.reservation_time')
->leftJoin('s_check_item', 's_list.entrust_code', '=', 's_check_item.item_code')
->where(['s_list.reg_num' => $mainInfo->reg_num, 's_list.list_status' => 1, 's_list.is_del' => 0, 's_list.is_nullify' => 0])
//排除自身,自己不能互斥自己
->whereNotIn('s_list.entrust_code', [$mainInfo->entrust_code])
->get();
if (count($status_1) > 0) {
foreach ($status_1 as $key => $value) {
foreach ($cha_hc as $k => $v) {
if ($v->code1 == $value->item_code or $v->code2 == $value->item_code) {
if ($v->time == 0) {
//如果是永久互斥,直接拒绝
return \Yz::Return(false, '当前预约项目与' . $value->entrust . '互斥,暂不可预约', ['name' => $value->entrust]);
}
if ($v->time > 0) {
//如果设置互斥时间,则判断预约时间是否超过 正在预约的最后时间段+互斥时间
$period = DB::table('s_period')->where(['id' => $value->reservation_time])->first();
$endTime = $period->period_end_time;
$periodEndDateTime = $value->reservation_date . ' ' . $endTime;
$date = new DateTime($periodEndDateTime);
// 添加互斥时间/小时
$date->add(new \DateInterval('PT' . $v->time . 'H')); // PTXH 表示X小时的时间间隔
$HuChi_EndDateTime = $date->format('Y-m-d H:i:s');//已经预约的项目结束互斥时间
$YuYueDateTime = substr($planInfo->date, 0, 10) . ' ' . $planInfo->begin_time;
if ($HuChi_EndDateTime > $YuYueDateTime) {
return \Yz::Return(false, '当前预约项目与' . $value->entrust . '互斥,暂不可预约,请预约' . $HuChi_EndDateTime . '后的日期', ['name' => $value->entrust]);
}
}
}
}
}
}
}
//校验检查项目时段规则
$itemRules = DB::table('s_check_item_rule')
->where(['target_type' => 'ITEM', 'target_id' => $item->id, 'rule_type' => 'TIME_RESTRICTION', 'status' => 1, 'deleted' => 0])
->get();
if ($itemRules->isNotEmpty()) {
$planWeekName = $planInfo->weekname;
$planBeginTime = $planInfo->begin_time;
$planResourcesId = $planInfo->resources_id;
// 筛选出适用于当前排班资源的规则
$applicableRules = [];
foreach ($itemRules as $rule) {
$rv = json_decode($rule->rule_value, true);
$resourcesId = $rv['resources_id'] ?? null;
if (empty($resourcesId)) {
// resources_id 为空,规则跳过
continue;
}
if ($resourcesId != $planResourcesId) {
// 指定了其他排班资源,跳过
continue;
}
$applicableRules[] = $rv;
}
// 无适用规则 → 不受限 → 通过
if (!empty($applicableRules)) {
$rulePass = false;
foreach ($applicableRules as $rv) {
$days = $rv['days'] ?? [];
$startTime = $rv['start_time'] ?? null;
$endTime = $rv['end_time'] ?? null;
if (!in_array($planWeekName, $days)) continue;
if ($startTime && $endTime && $planBeginTime) {
if ($planBeginTime >= $startTime && $planBeginTime < $endTime) {
$rulePass = true;
break;
}
} else {
$rulePass = true;
break;
}
}
if (!$rulePass) {
return \Yz::echoError1($item->item_name . ' 当前时段不在可预约时间范围内');
}
}
}
}
// 共享号源预计算(不依赖排班数据的部分)
$shareSlotIds = [];
$shareSlotMax = 0;
foreach ($itemSeatsData as $id => $data) { foreach ($itemSeatsData as $id => $data) {
if (!empty($data['is_share_slot'])) { $itemSeatsQueue[$id] = $data['use_seats'];
$shareSlotIds[] = $id;
$shareSlotMax = max($shareSlotMax, $data['use_seats']);
}
}
$maxShareSlotId = null;
$maxShareSeats = 0;
foreach ($itemSeatsData as $id => $data) {
if (!empty($data['is_share_slot']) && $data['use_seats'] > $maxShareSeats) {
$maxShareSeats = $data['use_seats'];
$maxShareSlotId = $id;
}
} }
// 以下变量在事务内计算 // 以下变量在事务内计算
$existingSharedMax = 0; $existingSharedMax = 0;
$additionalNeeded = 0; $additionalNeeded = 0;
@ -1359,6 +1156,12 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
$maxShareSlotId = $id; $maxShareSlotId = $id;
} }
} }
// 重建 itemSeatsQueue
$itemSeatsQueue = [];
foreach ($itemSeatsData as $id => $data) {
$itemSeatsQueue[$id] = $data['use_seats'];
}
} }
DB::beginTransaction(); DB::beginTransaction();
@ -1588,29 +1391,6 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme
} }
//判断某人这些待预约项目里,是否存在互斥
$userGroup=[];
foreach ($oldMainInfos as $key1 => $value1) {
$reg_num = $value1->reg_num;
if (!isset($userGroup[$reg_num])) {
$userGroup[$reg_num] = [];
}
$userGroup[$reg_num][]=[
'reg_num' => $value1->reg_num,
'user_name' => $value1->user_name,
'entrust_code' => $value1->entrust_code,
'entrust' => $value1->entrust,
];
}
// $huchiList
foreach ($userGroup as $personEntry) {
$ids = array_column($personEntry, 'entrust_code');
if ($this->isMutuallyExclusive($ids, $huchiList)) {
return \Yz::echoError1($personEntry[0]['user_name'].'勾选的项目存在互斥,不可同时预约');
}
}
// === 号源总量校验 + 精确扣减(替换原乐观锁扣减)=== // === 号源总量校验 + 精确扣减(替换原乐观锁扣减)===
// 加急预约允许超量,跳过总量校验 // 加急预约允许超量,跳过总量校验
@ -2950,4 +2730,575 @@ function generateQueueNumber($planId)
return $minAvailableNumber ?? $maxQueueNumber + 1; return $minAvailableNumber ?? $maxQueueNumber + 1;
} }
/**
* 预约前置条件校验(事务之前)
* 从 YuYue 方法中提取,供 YuYue 和 PreCheckYuYue 共用
* 返回结构化结果,不提前返回错误响应
*/
private function validateYuYueConditions($planid, $mainlistids, $do_type, $is_emergency = 0, $do_user = null)
{
date_default_timezone_set('PRC');
$nowdatetime = date("Y-m-d H:i:s");
$result = [
'pass' => true,
'fail_items' => [],
'planInfo' => null,
'resourceDeviceIds' => [],
'oldMainInfos' => [],
'itemSeatsData' => [],
'huchiList' => [],
'shareSlotIds' => [],
'shareSlotMax' => 0,
'maxShareSlotId' => null,
'is_emergency' => 0,
];
// 加急校验
if ($is_emergency == 1 && !empty($do_user)) {
$do_user_info = DB::table('users')->where('id', $do_user)->first();
if (!$do_user_info) {
$result['pass'] = false;
$result['fail_items'][] = ['mainlistid' => 0, 'item_name' => '', 'msg' => '无效的用户ID无法进行加急预约'];
return $result;
} else {
$special_privileges = [];
$decoded = json_decode($do_user_info->special_privileges, true);
if (is_array($decoded)) {
$special_privileges = $decoded;
}
if (!in_array('emergency', $special_privileges)) {
$result['pass'] = false;
$result['fail_items'][] = ['mainlistid' => 0, 'item_name' => '', 'msg' => '无权进行加急预约'];
return $result;
}
}
} else {
$is_emergency = 0;
}
$result['is_emergency'] = $is_emergency;
// 排班明细有效性
$planInfo = DB::table('s_source_roster_detail')->where(['id' => $planid, 'status' => 1, 'is_del' => 0])->first();
if (!$planInfo) {
$result['pass'] = false;
$result['fail_items'][] = ['mainlistid' => 0, 'item_name' => '', 'msg' => '当前时段不可用'];
return $result;
}
if ($nowdatetime > $planInfo->date . ' ' . $planInfo->end_reservation_time) {
$result['pass'] = false;
$result['fail_items'][] = ['mainlistid' => 0, 'item_name' => '', 'msg' => '已经超过预约截止时间'];
return $result;
}
$result['planInfo'] = $planInfo;
// 查询排班资源绑定的设备ID列表
$resourceDeviceIds = DB::table('s_department_resources_device')
->where('rsourece_id', $planInfo->resources_id)
->pluck('device_id')
->toArray();
$result['resourceDeviceIds'] = $resourceDeviceIds;
$itemSeatsData = [];
$huchiList = [];
$oldMainInfos = [];
// 遍历多个mainlistid
foreach ($mainlistids as $key_m => $mainlistid) {
$mainInfo = DB::table('s_list as a')
->select('a.*', DB::raw('COALESCE(a.reservation_begin_time, b.period_begin_time) as period_begin_time'), DB::raw('COALESCE(a.reservation_end_time, b.period_end_time) as period_end_time'))
->leftJoin('s_period as b', 'a.reservation_time', '=', 'b.id')
->where(['a.id' => $mainlistid])->first();
$oldMainInfos[] = $mainInfo;
if (!$mainInfo) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => '', 'msg' => '医嘱不存在'];
$result['pass'] = false;
continue;
}
// 状态校验
$msg_t = "";
if ($mainInfo->list_status == 0) $msg_t = "当前状态为待预约";
if ($mainInfo->list_status == 1) $msg_t = "已经在" . $mainInfo->reservation_date . '的' . $mainInfo->period_begin_time . '-' . $mainInfo->period_end_time . '预约成功,不能再次预约';
if ($mainInfo->list_status == 2) $msg_t = "当前状态为已登记";
if ($mainInfo->list_status == 3) $msg_t = "当前状态为已完成";
if ($do_type == 1 && $mainInfo->list_status <> 0) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $mainInfo->entrust, 'msg' => $mainInfo->entrust . ' ' . $msg_t . ',禁止预约'];
$result['pass'] = false;
continue;
}
if ($do_type == 2 && $mainInfo->list_status <> 1) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $mainInfo->entrust, 'msg' => $mainInfo->entrust . ' ' . $msg_t . ',不允许改约操作'];
$result['pass'] = false;
continue;
}
if ($do_type == 2 && $mainInfo->roster_id == $planid) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $mainInfo->entrust, 'msg' => $mainInfo->entrust . ' 改约时间与之前相同,无需改约'];
$result['pass'] = false;
continue;
}
// 病人类型匹配
$plan_patient_type = explode(",", $planInfo->patient_type);
if (!in_array($mainInfo->patient_type, $plan_patient_type)) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $mainInfo->entrust, 'msg' => '当前计划不支持此病人类型'];
$result['pass'] = false;
continue;
}
// 检查项目可用性
$deptId = $planInfo->department_id ?? 0;
$item = DB::table('s_check_item')
->leftJoin('s_dept_check_item', function($j) use ($deptId) {
$j->on('s_dept_check_item.check_item_id', '=', 's_check_item.id')
->where('s_dept_check_item.department_id', '=', $deptId);
})
->where(['s_check_item.item_code' => $mainInfo->entrust_code, 's_check_item.status' => 1, 's_check_item.is_del' => 0])
->select('s_check_item.*',
DB::raw('COALESCE(s_dept_check_item.use_seats, s_check_item.use_seats, 1) as use_seats'),
DB::raw('COALESCE(s_dept_check_item.is_share_slot, s_check_item.is_share_slot, 0) as is_share_slot'),
DB::raw('COALESCE(s_dept_check_item.check_begin_time, s_check_item.check_begin_time, 0) as check_begin_time'),
DB::raw('COALESCE(s_dept_check_item.limosis, s_check_item.limosis) as limosis'))
->first();
if (!$item) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $mainInfo->entrust, 'msg' => '此检查项目不可用'];
$result['pass'] = false;
continue;
}
// 设备匹配
$itemDeviceIds = DB::table('s_check_item_device')
->where('item_id', $item->id)
->pluck('device_id')
->toArray();
if (empty(array_intersect($itemDeviceIds, $resourceDeviceIds))) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $item->item_name, 'msg' => $item->item_name . ' 不支持在当前检查资源上预约'];
$result['pass'] = false;
continue;
}
$itemSeatsData[$mainlistid] = [
'use_seats' => $item->use_seats,
'is_share_slot' => $item->is_share_slot ?? 0,
];
// 预约等待期
$entrust_time = $mainInfo->entrust_date . ' ' . $mainInfo->entrust_time;
$date = new DateTime($entrust_time);
$date->modify("+" . $item->check_begin_time . " minutes");
$enableCheckTime = $date;
$plan_dateTime = $planInfo->date . ' ' . $planInfo->end_reservation_time;
$plan_dateTime = new DateTime($plan_dateTime);
if ($plan_dateTime < $enableCheckTime and $item->check_begin_time > 0) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $item->item_name, 'msg' => $item->item_name . " 已设置只能预约医嘱开具后" . $item->check_begin_time . "分钟后的时间,请预约" . $enableCheckTime->format("Y-m-d H:i:s") . "之后的时间"];
$result['pass'] = false;
continue;
}
// 空腹校验
$configs = DB::table('configs')->where('label', '开启空腹')->first();
if ($configs && $item->limosis == 1 and $planInfo->end_reservation_time > '12:00:00' and $configs->value == 1) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $item->item_name, 'msg' => $item->item_name . ' 项目必须空腹,只能预约上午,请重新选择时间'];
$result['pass'] = false;
continue;
}
// 互斥校验
$cha_hc = DB::table('s_huchi')->where('is_del', 0)
->where(function ($q) use ($item) {
$q->where(['code1' => $item->item_code])->orWhere('code2', $item->item_code);
})->get();
foreach ($cha_hc as $hc) {
$huchiList[] = [$hc->code1, $hc->code2];
}
if (count($cha_hc) > 0) {
$status_1 = DB::table('s_list')
->select('s_check_item.item_code', 's_list.entrust', 's_list.reservation_date', 's_list.reservation_time')
->leftJoin('s_check_item', 's_list.entrust_code', '=', 's_check_item.item_code')
->where(['s_list.reg_num' => $mainInfo->reg_num, 's_list.list_status' => 1, 's_list.is_del' => 0, 's_list.is_nullify' => 0])
->whereNotIn('s_list.entrust_code', [$mainInfo->entrust_code])
->get();
if (count($status_1) > 0) {
$huchiFail = false;
foreach ($status_1 as $key => $value) {
foreach ($cha_hc as $k => $v) {
if ($v->code1 == $value->item_code or $v->code2 == $value->item_code) {
if ($v->time == 0) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $item->item_name, 'msg' => '当前预约项目与' . $value->entrust . '互斥,暂不可预约'];
$result['pass'] = false;
$huchiFail = true;
break 2;
}
if ($v->time > 0) {
$period = DB::table('s_period')->where(['id' => $value->reservation_time])->first();
$endTime = $period->period_end_time;
$periodEndDateTime = $value->reservation_date . ' ' . $endTime;
$date = new DateTime($periodEndDateTime);
$date->add(new \DateInterval('PT' . $v->time . 'H'));
$HuChi_EndDateTime = $date->format('Y-m-d H:i:s');
$YuYueDateTime = substr($planInfo->date, 0, 10) . ' ' . $planInfo->begin_time;
if ($HuChi_EndDateTime > $YuYueDateTime) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $item->item_name, 'msg' => '当前预约项目与' . $value->entrust . '互斥,暂不可预约,请预约' . $HuChi_EndDateTime . '后的日期'];
$result['pass'] = false;
$huchiFail = true;
break 2;
}
}
}
}
}
if ($huchiFail) continue;
}
}
// 时段规则校验
$itemRules = DB::table('s_check_item_rule')
->where(['target_type' => 'ITEM', 'target_id' => $item->id, 'rule_type' => 'TIME_RESTRICTION', 'status' => 1, 'deleted' => 0])
->get();
if ($itemRules->isNotEmpty()) {
$planWeekName = $planInfo->weekname;
$planBeginTime = $planInfo->begin_time;
$planResourcesId = $planInfo->resources_id;
$applicableRules = [];
foreach ($itemRules as $rule) {
$rv = json_decode($rule->rule_value, true);
$resourcesId = $rv['resources_id'] ?? null;
if (empty($resourcesId)) continue;
if ($resourcesId != $planResourcesId) continue;
$applicableRules[] = $rv;
}
if (!empty($applicableRules)) {
$rulePass = false;
foreach ($applicableRules as $rv) {
$days = $rv['days'] ?? [];
$startTime = $rv['start_time'] ?? null;
$endTime = $rv['end_time'] ?? null;
if (!in_array($planWeekName, $days)) continue;
if ($startTime && $endTime && $planBeginTime) {
if ($planBeginTime >= $startTime && $planBeginTime < $endTime) {
$rulePass = true;
break;
}
} else {
$rulePass = true;
break;
}
}
if (!$rulePass) {
$result['fail_items'][] = ['mainlistid' => $mainlistid, 'item_name' => $item->item_name, 'msg' => $item->item_name . ' 当前时段不在可预约时间范围内'];
$result['pass'] = false;
continue;
}
}
}
}
$result['oldMainInfos'] = $oldMainInfos;
$result['itemSeatsData'] = $itemSeatsData;
$result['huchiList'] = $huchiList;
// 共享号源预计算
$shareSlotIds = [];
$shareSlotMax = 0;
foreach ($itemSeatsData as $id => $data) {
if (!empty($data['is_share_slot'])) {
$shareSlotIds[] = $id;
$shareSlotMax = max($shareSlotMax, $data['use_seats']);
}
}
$maxShareSlotId = null;
$maxShareSeats = 0;
foreach ($itemSeatsData as $id => $data) {
if (!empty($data['is_share_slot']) && $data['use_seats'] > $maxShareSeats) {
$maxShareSeats = $data['use_seats'];
$maxShareSlotId = $id;
}
}
$result['shareSlotIds'] = $shareSlotIds;
$result['shareSlotMax'] = $shareSlotMax;
$result['maxShareSlotId'] = $maxShareSlotId;
// 批次内互斥校验
if ($result['pass'] && count($oldMainInfos) > 0) {
$userGroup = [];
foreach ($oldMainInfos as $key1 => $value1) {
$reg_num = $value1->reg_num;
if (!isset($userGroup[$reg_num])) {
$userGroup[$reg_num] = [];
}
$userGroup[$reg_num][] = [
'reg_num' => $value1->reg_num,
'user_name' => $value1->user_name,
'entrust_code' => $value1->entrust_code,
'entrust' => $value1->entrust,
];
}
foreach ($userGroup as $personEntry) {
$ids = array_column($personEntry, 'entrust_code');
if ($this->isMutuallyExclusive($ids, $huchiList)) {
$result['fail_items'][] = ['mainlistid' => 0, 'item_name' => $personEntry[0]['user_name'], 'msg' => $personEntry[0]['user_name'] . '勾选的项目存在互斥,不可同时预约'];
$result['pass'] = false;
}
}
}
return $result;
}
/**
* 预约前预检接口:条件校验 + 余量判断(只读),不扣减、不回写 HIS
* 入参:{groups, appointment_type, do_type, is_emergency, do_user}
* 返回:预检结果列表
*/
public function PreCheckYuYue($groups, $appointment_type, $do_type, $is_emergency = 0, $do_user = null)
{
date_default_timezone_set('PRC');
$nowdatetime = date("Y-m-d H:i:s");
$allList = [];
$hasMismatch = false;
$hasFail = false;
foreach ($groups as $group) {
$planid = $group['planid'];
$mainlistids = $group['mainlistid'];
if (!is_array($mainlistids)) $mainlistids = [$mainlistids];
// 复用 validateYuYueConditions 进行条件校验
$validation = $this->validateYuYueConditions($planid, $mainlistids, $do_type, $is_emergency, $do_user);
$planInfo = $validation['planInfo'];
$isEmergency = $validation['is_emergency'];
// 查询排班资源信息,用于组装返回数据
$resource = null;
$appointmentDeptCode = '';
$appointmentDeptName = '';
if ($planInfo) {
$resource = DB::table('s_department_resources')->where('id', $planInfo->resources_id)->first();
if ($resource) {
$deptId = $resource->execute_department_id ?: $resource->department_id;
if ($deptId) {
$dept = DB::table('s_department')->where('id', $deptId)->first();
if ($dept) {
$appointmentDeptCode = $dept->department_number;
$appointmentDeptName = $dept->department_name;
}
}
}
}
// 若条件校验失败,将失败项记录,从 fail_items 构建
if (!$validation['pass']) {
// 区分组级失败还是项目级失败
$groupLevelFail = [];
$itemLevelFail = [];
foreach ($validation['fail_items'] as $fi) {
if ($fi['mainlistid'] == 0) {
$groupLevelFail[] = $fi;
} else {
$itemLevelFail[$fi['mainlistid']] = $fi;
}
}
// 组级失败:整组所有项目标红
if (!empty($groupLevelFail)) {
foreach ($mainlistids as $mid) {
$mainInfo = DB::table('s_list')->where('id', $mid)->first();
$item_name = $mainInfo ? $mainInfo->entrust : '';
$entrustDeptCode = $mainInfo ? $mainInfo->RISRAcceptDeptCode : '';
$entrustDeptName = '';
if ($entrustDeptCode) {
$deptInfo = DB::table('s_department')->where('department_number', $entrustDeptCode)->first();
if ($deptInfo) $entrustDeptName = $deptInfo->department_name;
}
$mismatch = ($entrustDeptCode !== '' && $appointmentDeptCode !== '' && $entrustDeptCode !== $appointmentDeptCode);
if ($mismatch) $hasMismatch = true;
$hasFail = true;
$allList[] = [
'item_name' => $item_name,
'entrust_dept_code' => $entrustDeptCode,
'entrust_dept_name' => $entrustDeptName,
'appointment_dept_code' => $appointmentDeptCode,
'appointment_dept_name' => $appointmentDeptName,
'dept_mismatch' => $mismatch,
'appointment_date' => $planInfo ? $planInfo->date : '',
'begin_time' => $planInfo ? $planInfo->begin_time : '',
'end_time' => $planInfo ? $planInfo->end_time : '',
'check_status' => 'fail',
'check_msg' => $groupLevelFail[0]['msg'],
];
}
} else {
// 项目级失败:失败项标红,通过项标绿
foreach ($mainlistids as $mid) {
$mainInfo = DB::table('s_list')->where('id', $mid)->first();
$item_name = $mainInfo ? $mainInfo->entrust : '';
$entrustDeptCode = $mainInfo ? $mainInfo->RISRAcceptDeptCode : '';
$entrustDeptName = '';
if ($entrustDeptCode) {
$deptInfo = DB::table('s_department')->where('department_number', $entrustDeptCode)->first();
if ($deptInfo) $entrustDeptName = $deptInfo->department_name;
}
$mismatch = ($entrustDeptCode !== '' && $appointmentDeptCode !== '' && $entrustDeptCode !== $appointmentDeptCode);
if ($mismatch) $hasMismatch = true;
$failMsg = '';
$checkStatus = 'pass';
if (isset($itemLevelFail[$mid])) {
$failMsg = $itemLevelFail[$mid]['msg'];
$checkStatus = 'fail';
$hasFail = true;
}
$allList[] = [
'item_name' => $item_name,
'entrust_dept_code' => $entrustDeptCode,
'entrust_dept_name' => $entrustDeptName,
'appointment_dept_code' => $appointmentDeptCode,
'appointment_dept_name' => $appointmentDeptName,
'dept_mismatch' => $mismatch,
'appointment_date' => $planInfo ? $planInfo->date : '',
'begin_time' => $planInfo ? $planInfo->begin_time : '',
'end_time' => $planInfo ? $planInfo->end_time : '',
'check_status' => $checkStatus,
'check_msg' => $failMsg,
];
}
}
continue;
}
// 条件校验通过,进行只读余量判断
$itemSeatsData = $validation['itemSeatsData'];
$shareSlotIds = $validation['shareSlotIds'];
$shareSlotMax = $validation['shareSlotMax'];
$maxShareSlotId = $validation['maxShareSlotId'];
$oldMainInfos = $validation['oldMainInfos'];
// 渠道合并查询
$appointment_types = [$appointment_type];
$appointment_type_link = DB::table('s_appointment_type_ratio')->where(['department_id' => $planInfo->department_id, 'appointment_type_id' => $appointment_type])->first();
if (!empty($appointment_type_link->link)) {
$appointment_types = json_decode($appointment_type_link->link, true);
$appointment_types = array_merge($appointment_types, [$appointment_type]);
sort($appointment_types);
}
// 只读查询号源余量
$roster_detail_counts = DB::table('s_source_roster_detail_count')
->where('roster_detail_id', $planid)
->whereIn('appointment_type_id', $appointment_types)
->orderBy('appointment_type_id', 'asc')
->get();
// 共享号源计算(只读)
$existingSharedMax = 0;
$additionalNeeded = 0;
$zhanweiCount = 0;
$regNum = $oldMainInfos[0]->reg_num ?? null;
if (!empty($shareSlotIds) && $regNum) {
$existingSharedItems = DB::table('s_list')
->join('s_check_item', 's_list.entrust_code', '=', 's_check_item.item_code')
->leftJoin('s_dept_check_item', function($j) {
$j->on('s_dept_check_item.check_item_id', '=', 's_check_item.id')
->on('s_dept_check_item.department_id', '=', 's_list.department_id');
})
->where('s_list.reg_num', $regNum)
->where('s_list.roster_id', $planid)
->where('s_list.list_status', 1)
->where(DB::raw('COALESCE(s_dept_check_item.is_share_slot, s_check_item.is_share_slot, 0)'), 1)
->select(DB::raw('COALESCE(s_dept_check_item.use_seats, s_check_item.use_seats, 1) as use_seats'))
->get();
if ($existingSharedItems->isNotEmpty()) {
$existingSharedMax = $existingSharedItems->max('use_seats');
}
$overallMax = max($existingSharedMax, $shareSlotMax);
$additionalNeeded = max(0, $overallMax - $existingSharedMax);
}
foreach ($itemSeatsData as $id => $data) {
if (!empty($data['is_share_slot'])) {
if ($id === $maxShareSlotId) {
$zhanweiCount += $additionalNeeded;
}
} else {
$zhanweiCount += $data['use_seats'];
}
}
// 余量判断:非紧急模式下,总余量是否满足占位需求
$quotaFail = false;
$quotaMsg = '';
if ($isEmergency !== 1) {
$totalAvailable = 0;
foreach ($roster_detail_counts as $rdc) {
$physicalAvailable = $rdc->count - $rdc->used_count - ($rdc->locked_count ?? 0);
$totalAvailable += max(0, $physicalAvailable);
}
if ($totalAvailable < $zhanweiCount) {
$quotaFail = true;
$quotaMsg = '当前预约时间名额不足,剩余' . $totalAvailable . ',需要' . $zhanweiCount;
}
}
// 组装返回数据
foreach ($mainlistids as $mid) {
$mainInfo = null;
foreach ($oldMainInfos as $omi) {
if ($omi && $omi->id == $mid) {
$mainInfo = $omi;
break;
}
}
$item_name = $mainInfo ? $mainInfo->entrust : '';
$entrustDeptCode = $mainInfo ? $mainInfo->RISRAcceptDeptCode : '';
$entrustDeptName = '';
if ($entrustDeptCode) {
$deptInfo = DB::table('s_department')->where('department_number', $entrustDeptCode)->first();
if ($deptInfo) $entrustDeptName = $deptInfo->department_name;
}
// 查询检查项目名称
if ($mainInfo) {
$checkItem = DB::table('s_check_item')->where(['item_code' => $mainInfo->entrust_code, 'status' => 1, 'is_del' => 0])->first();
if ($checkItem) $item_name = $checkItem->item_name;
}
$mismatch = ($entrustDeptCode !== '' && $appointmentDeptCode !== '' && $entrustDeptCode !== $appointmentDeptCode);
if ($mismatch) $hasMismatch = true;
$checkStatus = 'pass';
$checkMsg = '';
if ($quotaFail) {
$checkStatus = 'fail';
$checkMsg = $quotaMsg;
$hasFail = true;
}
$allList[] = [
'item_name' => $item_name,
'entrust_dept_code' => $entrustDeptCode,
'entrust_dept_name' => $entrustDeptName,
'appointment_dept_code' => $appointmentDeptCode,
'appointment_dept_name' => $appointmentDeptName,
'dept_mismatch' => $mismatch,
'appointment_date' => $planInfo ? $planInfo->date : '',
'begin_time' => $planInfo ? $planInfo->begin_time : '',
'end_time' => $planInfo ? $planInfo->end_time : '',
'check_status' => $checkStatus,
'check_msg' => $checkMsg,
];
}
}
return \Yz::Return(true, '检查完成', [
'has_mismatch' => $hasMismatch,
'has_fail' => $hasFail,
'list' => $allList,
]);
}
} }

@ -181,6 +181,7 @@
Route::post('admin/GetEnablePlan','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetEnablePlan');//获取可用的计划,用于计划占用 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/GetOptimalPlan','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetOptimalPlan');//最优时间/单天全检 分配
Route::post('admin/PlanYuYue','App\Http\Controllers\API\Admin\YeWu\PlanListController@YuYue');//开始预约 Route::post('admin/PlanYuYue','App\Http\Controllers\API\Admin\YeWu\PlanListController@YuYue');//开始预约
Route::post('admin/PreCheckYuYue','App\Http\Controllers\API\Admin\YeWu\PlanListController@PreCheckYuYue');//预约前预检
Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约 Route::post('admin/CheckEntrstItemGroup','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckEntrstItemGroup');//批量检查医嘱检查项目是否可以同时预约
Route::post('admin/CheckIsDaiJian','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckIsDaiJian');//检查当前时段是否有存在已经预约的待检查项目 Route::post('admin/CheckIsDaiJian','App\Http\Controllers\API\Admin\YeWu\WorkMainController@CheckIsDaiJian');//检查当前时段是否有存在已经预约的待检查项目
Route::post('admin/GetPlanUsedList','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetUsedList');//计划占用详情列表 Route::post('admin/GetPlanUsedList','App\Http\Controllers\API\Admin\YeWu\PlanListController@GetUsedList');//计划占用详情列表

@ -578,6 +578,10 @@ export const GetEnablePlan = (data = {}) => {
export const GetOptimalPlan = (data = {}) => { export const GetOptimalPlan = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'admin/GetOptimalPlan', data: data }) return axios({ url: import.meta.env.VITE_APP_API + 'admin/GetOptimalPlan', data: data })
} }
//预约前预检
export const PreCheckYuYue = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'admin/PreCheckYuYue', data: data })
}
//获取某日可用号源 //获取某日可用号源
export const PlanYuYue = (data = {}) => { export const PlanYuYue = (data = {}) => {
return axios({ url: import.meta.env.VITE_APP_API + 'admin/PlanYuYue', data: data }) return axios({ url: import.meta.env.VITE_APP_API + 'admin/PlanYuYue', data: data })

@ -224,6 +224,39 @@
<el-button type="primary" @click="recommendResultVisible = false">确定</el-button> <el-button type="primary" @click="recommendResultVisible = false">确定</el-button>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="preCheckDialogVisible" title="预约确认" width="800" :close-on-click-modal="false" @keyup.enter="preCheckConfirm">
<el-alert v-if="preCheckHasMismatch" type="warning" :closable="false" show-icon style="margin-bottom: 12px;">
<template #title>医嘱选择的执行科室与预约选择的执行科室不一致请确认</template>
</el-alert>
<el-table :data="preCheckList" style="width: 100%;" max-height="400" :row-class-name="preCheckRowClassName">
<el-table-column prop="item_name" label="项目名称" min-width="140" />
<el-table-column label="执行科室" min-width="180">
<template #default="scope">
<div :style="{ color: scope.row.dept_mismatch ? '#f56c6c' : 'inherit' }">
{{ scope.row.entrust_dept_name }}
<span v-if="scope.row.dept_mismatch" style="color: #f56c6c;"> {{ scope.row.appointment_dept_name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="检查时间" min-width="160">
<template #default="scope">
<div>{{ scope.row.appointment_date }} {{ scope.row.begin_time?.substring(0, 5) }}-{{ scope.row.end_time?.substring(0, 5) }}</div>
<div v-if="scope.row.check_status === 'fail'" style="color: #f56c6c; font-size: 12px; margin-top: 2px;">{{ scope.row.check_msg }}</div>
</template>
</el-table-column>
<el-table-column label="校验结果" width="80">
<template #default="scope">
<el-tag v-if="scope.row.check_status === 'pass'" type="success"></el-tag>
<el-tag v-else type="danger">失败</el-tag>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button v-if="!preCheckHasFail" @click="preCheckDialogVisible = false"></el-button>
<el-button v-if="!preCheckHasFail" type="primary" @click="preCheckConfirm"></el-button>
<el-button v-if="preCheckHasFail" type="primary" @click="preCheckDialogVisible = false"></el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
@ -238,6 +271,7 @@
GetEnablePlan, GetEnablePlan,
CheckIsDaiJian, CheckIsDaiJian,
PlanYuYue, PlanYuYue,
PreCheckYuYue,
CheckEntrstItemGroup2, CheckEntrstItemGroup2,
DoctorCancelYuYue,GetConfigInfo2,GetOptimalPlan, DoctorCancelYuYue,GetConfigInfo2,GetOptimalPlan,
BatchCancelYuYue BatchCancelYuYue
@ -323,6 +357,20 @@ let TanChuangMsgDialogVisible=ref(false)
let resultDialogHtml = ref('') let resultDialogHtml = ref('')
let recommendResultVisible = ref(false) let recommendResultVisible = ref(false)
let recommendResultHtml = ref('') let recommendResultHtml = ref('')
let preCheckDialogVisible = ref(false)
let preCheckList = ref([])
let preCheckHasMismatch = ref(false)
let preCheckHasFail = ref(false)
let preCheckDotype = ref(null)
const preCheckConfirm = () => {
preCheckDialogVisible.value = false
startYuYue(preCheckDotype.value)
}
const preCheckRowClassName = ({ row }) => {
if (row.check_status === 'fail') return 'precheck-fail-row'
return ''
}
const getWeekday = (date1) => { const getWeekday = (date1) => {
let days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; let days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const date = new Date(date1); // : const date = new Date(date1); // :
@ -1451,7 +1499,7 @@ let TanChuangMsgDialogVisible=ref(false)
} }
}) })
} }
const YuYueButtonClick = (type) => { const YuYueButtonClick = async (type) => {
if (selectedEntrustId.length == 0) { if (selectedEntrustId.length == 0) {
ElMessage.error("请选择检查项目") ElMessage.error("请选择检查项目")
return false; return false;
@ -1461,25 +1509,35 @@ let TanChuangMsgDialogVisible=ref(false)
return false; return false;
} }
let msg='确定预约此时间吗?' // groups
if(type==2){ const groupMap = {}
msg='确定改约至此时间吗?' selectedMianListId.value.forEach(id => {
} const row = entrustTableDate.value.find(v => v.id === id)
ElMessageBox.confirm( const planId = row?.temp_plan_id || row?.roster_id
msg, if (!planId) return
'提示', { if (!groupMap[planId]) groupMap[planId] = []
confirmButtonText: '确定', groupMap[planId].push(id)
cancelButtonText: '取消', })
type: 'warning', const groups = Object.entries(groupMap).map(([planid, mainlistid]) => ({ planid: Number(planid), mainlistid }))
}
)
.then(() => {
startYuYue(type)
})
.catch(() => {
}) //
const res = await PreCheckYuYue({
groups,
appointment_type: props.appointment_type,
dotype: type,
do_user: props.do_user,
is_emergency: type == 111 ? 1 : null
})
if (!res.status) {
ElMessage.error(res.msg)
return
}
preCheckList.value = res.data.list || []
preCheckHasMismatch.value = res.data.has_mismatch || false
preCheckHasFail.value = res.data.has_fail || false
preCheckDotype.value = type
preCheckDialogVisible.value = true
} }
const startYuYue = async (type) => { const startYuYue = async (type) => {
planLoading.value = true planLoading.value = true
@ -2130,6 +2188,10 @@ let TanChuangMsgDialogVisible=ref(false)
background-color: #9cd0de; background-color: #9cd0de;
} }
/** 预检弹窗失败行标红 */
.precheck-fail-row {
background-color: #fef0f0 !important;
}
</style> </style>
<style> <style>

Loading…
Cancel
Save