@ -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,
]);
}
}
}