diff --git a/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php b/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php index 80a425f..0222eea 100644 --- a/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php +++ b/Laravel/app/Http/Controllers/API/Admin/YeWu/WorkMainController.php @@ -131,6 +131,10 @@ class WorkMainController extends Controller $list=$list->where(['reservation_department'=>$department->department_name]); }else{ $list=$list->where(['RISRAcceptDeptCode'=>$department->department_number]); + $list = $list->where(function ($q) use($department) { + $q->where(['RISRAcceptDeptCode'=>$department->department_number]) + ->orWhere('RISRAcceptDeptCode', $department->department_number); + }); } if ($searchInfo['dateRange']!=null and count($searchInfo['dateRange']) == 2) { $list = $list->where(function ($q) use($searchInfo) { diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index 1e20c13..ce02d9e 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -219,6 +219,7 @@ WHERE if ($nowdatetime > $planInfo->date . ' ' . $planInfo->end_reservation_time) return \Yz::echoError1('已经超过预约截止时间'); $zhanweiCount=0;//各个检查项目需要占用检查名额的总和 + $itemSeatsQueue = []; // 队列:按顺序存储每个项目需要的座位数 [项目ID => 座位数] $huchiList=[];//互斥item_id对应关系 $oldMainInfos = [];//临时存储原来的主表信息,用于改约 //遍历多个s_list表id,前端多选,一次预约多个检查项目 @@ -255,6 +256,7 @@ WHERE //判断互斥(暂时根据reg_num判断身份) //查询想要预约的项目 其自身code $item = DB::table('s_check_item')->where(['item_code' => $mainInfo->entrust_code, 'status' => 1, 'is_del' => 0])->first(); + $itemSeatsQueue[$mainlistid]=$item->use_seats; if (!$item) return \Yz::echoError1('此检查项目不可用'); $zhanweiCount+=$item->use_seats; //医嘱开具后,预约时间需在设定的等待期之后,单位分钟 @@ -319,7 +321,6 @@ WHERE } - DB::beginTransaction(); try { @@ -343,32 +344,131 @@ WHERE $planZongLockedCount=0;//合并后的各个渠道占位名额 $plan_qudao_tempCount=[];//拆分各个渠道需要占用的名额 $weifenpeiCount=$zhanweiCount;//未分配的名额 - foreach ($roster_detail_counts as $roster_detail_count) { - $planZongCount+=$roster_detail_count->count; - $planZongUsedCount+=$roster_detail_count->used_count; - $planZongLockedCount+=($roster_detail_count->locked_count ?? 0); - $keyongCount=$roster_detail_count->count - $roster_detail_count->used_count - ($roster_detail_count->locked_count ?? 0); - if($weifenpeiCount-$keyongCount>=0){ - $plan_qudao_tempCount[]=$keyongCount; - $weifenpeiCount-=$keyongCount; - }else{ - $plan_qudao_tempCount[]=$weifenpeiCount; - $weifenpeiCount=0; - } + + $itemChannelMap = []; + // 初始化 map + foreach ($mainlistids as $mid) { + $itemChannelMap[$mid] = []; } - if ($planZongCount < ($planZongUsedCount + $planZongLockedCount + $zhanweiCount)){ - if($is_emergency!==1){ - return \Yz::echoError1('当前预约时间名额不足'); - }else{ //如果是紧急预约,则把未分配的 强制加到当前预约渠道 - foreach ($roster_detail_counts as $key=> $roster_detail_count) { - if($roster_detail_count->appointment_type_id==$appointment_type){ - $plan_qudao_tempCount[$key]=$weifenpeiCount; + // 指针:当前正在分配哪个项目 + reset($itemSeatsQueue); + $currentItemId = key($itemSeatsQueue); + $currentItemRemaining = $currentItemId ? $itemSeatsQueue[$currentItemId] : 0; + $targetChannelKey = -1; // 记录紧急模式下目标渠道的 Key + + foreach ($roster_detail_counts as $key => $roster_detail_count) { + $channelId = $roster_detail_count->id; + + // 统计总量 + $planZongCount += $roster_detail_count->count; + $planZongUsedCount += $roster_detail_count->used_count; + $planZongLockedCount += ($roster_detail_count->locked_count ?? 0); + + // 计算物理可用名额 + $physicalAvailable = $roster_detail_count->count - $roster_detail_count->used_count - ($roster_detail_count->locked_count ?? 0); + + // 确定该渠道本次能贡献的名额 (不能超过剩余需求,也不能超过物理可用) + $contribCount = 0; + if ($physicalAvailable > 0 && $weifenpeiCount > 0) { + $contribCount = min($physicalAvailable, $weifenpeiCount); + } + + $plan_qudao_tempCount[$key] = $contribCount; + $weifenpeiCount -= $contribCount; // 更新剩余未分配名额 + + // 记录目标渠道 Key (用于紧急模式) + if ($roster_detail_count->appointment_type_id == $appointment_type) { + $targetChannelKey = $key; + } + + // --- 执行流式分配 (填充 itemChannelMap) --- + if ($contribCount > 0 && $currentItemId !== null) { + $remainingInChannel = $contribCount; + + while ($remainingInChannel > 0 && $currentItemId !== null) { + $take = min($currentItemRemaining, $remainingInChannel); + + if (!isset($itemChannelMap[$currentItemId][$channelId])) { + $itemChannelMap[$currentItemId][$channelId] = 0; + } + $itemChannelMap[$currentItemId][$channelId] += $take; + + $currentItemRemaining -= $take; + $remainingInChannel -= $take; + + // 项目已满,切换下一个 + if ($currentItemRemaining <= 0) { + next($itemSeatsQueue); + $nextId = key($itemSeatsQueue); + if ($nextId !== null) { + $currentItemId = $nextId; + $currentItemRemaining = $itemSeatsQueue[$currentItemId]; + } else { + $currentItemId = null; + break; + } } } } } + if ($is_emergency == 1 && $weifenpeiCount > 0) { + // 如果还有剩余名额没分出去,且找到了目标渠道 + if ($targetChannelKey != -1) { + $targetKey = $targetChannelKey; + $targetChannelId = $roster_detail_counts[$targetKey]->id; + + // 【关键】:累加剩余名额到目标渠道 + // 注意:这里是 += 而不是 =,因为该渠道在第一阶段可能已经贡献了一部分 + $plan_qudao_tempCount[$targetKey] += $weifenpeiCount; + + // 【重要】:必须同步更新 itemChannelMap + // 将剩余的 $weifenpeiCount 继续分配给当前还没分满的项目指针 + $remainingEmergency = $weifenpeiCount; + + while ($remainingEmergency > 0 && $currentItemId !== null) { + $take = min($currentItemRemaining, $remainingEmergency); + + if (!isset($itemChannelMap[$currentItemId][$targetChannelId])) { + $itemChannelMap[$currentItemId][$targetChannelId] = 0; + } + $itemChannelMap[$currentItemId][$targetChannelId] += $take; + + $currentItemRemaining -= $take; + $remainingEmergency -= $take; + + // 项目已满,切换下一个 + if ($currentItemRemaining <= 0) { + next($itemSeatsQueue); + $nextId = key($itemSeatsQueue); + if ($nextId !== null) { + $currentItemId = $nextId; + $currentItemRemaining = $itemSeatsQueue[$currentItemId]; + } else { + $currentItemId = null; + break; + } + } + } + + // 紧急模式下,剩余未分配名额归零(因为都压到目标渠道了) + $weifenpeiCount = 0; + } else { + // 极端情况:紧急模式但没找到对应 appointment_type 的渠道 + return \Yz::echoError1('紧急预约失败:未找到匹配的预约渠道配置'); + } + } + // --- 3. 最终校验 --- + if ($weifenpeiCount > 0) { + // 如果不是紧急模式,或者紧急模式但没找到渠道导致还有剩余 + if ($is_emergency !== 1) { + return \Yz::echoError1('当前预约时间名额不足'); + } else { + // 紧急模式下理论上不应走到这里,除非配置错误 + return \Yz::echoError1('紧急预约分配异常:无法容纳所有名额'); + } + } //判断某人这些待预约项目里,是否存在互斥 @@ -397,102 +497,88 @@ WHERE //更新计划明细表使用数量 $up_plan_count_all_success =true; - $appointment_use_plan_detail_arr=[];//预约各个号源占用号源详情 foreach ($roster_detail_counts as $key => $planCount) { - if($plan_qudao_tempCount[$key]==0) continue; - $currentVersion = $planCount->version ?? 0; - - $query = DB::table('s_source_roster_detail_count') - ->where(['id' => $planCount->id]) - ->where('version', $currentVersion); - // 【核心修改】只有非紧急预约时,才检查名额是否充足 - if ($is_emergency!==1) { - $query->whereRaw('count >= (used_count + IFNULL(locked_count, 0) + ?)', [$plan_qudao_tempCount[$key]]); - } - // $u = $query->increment('used_count', $plan_qudao_tempCount[$key]); - $u = $query->update([ - 'used_count' => DB::raw("used_count + {$plan_qudao_tempCount[$key]}"), - 'version' => DB::raw("version + 1"), - // 如果需要更新更新时间,可以在这里加 'updated_at' => now() - ]); - - if(!$u){ - $up_plan_count_all_success=false; - break; + $countToDeduct = $plan_qudao_tempCount[$key]; + if ($countToDeduct == 0) continue; + + $query = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id]); + + // 非紧急模式:严格检查库存充足 + if ($is_emergency !== 1) { + // 只有当 剩余物理库存 >= 本次扣减 时才执行 + // 公式:count - used - locked >= countToDeduct => count >= used + locked + countToDeduct + $query->whereRaw('count >= (used_count + IFNULL(locked_count, 0) + ?)', [$countToDeduct]); } - $appointment_use_plan_detail_arr[]=[ - "roster_detail_count_id"=>$planCount->id, - "count"=>$plan_qudao_tempCount[$key], - ]; - } + // 紧急模式:不加 whereRaw 限制,允许超卖 (used_count 可以大于 count) + + $affected = $query->increment('used_count', $countToDeduct); - if ($up_plan_count_all_success) { - foreach ($roster_detail_counts as $key => $planCount) { - $cha = DB::table('s_source_roster_detail_count')->where(['id' => $planCount->id])->first(); - if ($is_emergency!==1) { - if ($cha->count < $cha->used_count + ($cha->locked_count ?? 0)) { - DB::rollBack(); - return \Yz::echoError1('操作失败1'); + if ($affected == 0) { + if ($is_emergency !== 1) { + // 非紧急模式扣减失败,说明并发导致库存不足 + $up_plan_count_all_success = false; + break; + } else { + // 紧急模式扣减失败,通常是因为记录不存在,需检查 + $check = DB::table('s_source_roster_detail_count')->find($planCount->id); + if (!$check) { + $up_plan_count_all_success = false; + break; } + // 如果记录存在但 affected=0 (极少见,可能是行锁超时等),在紧急模式下我们尝试强制更新 + // 这里为了简单,假设 increment 只要记录存在就会成功返回 1 (即使超卖) + // 如果确实返回 0 且记录存在,可能需要重试或报错,视具体 DB 行为而定 + // 大多数情况下,不加 whereRaw 的 increment 都会成功 } - } - }else{ - DB::rollBack(); - return \Yz::echoError1('操作失败'); - } - - //排队号 - $xvhao=0; - $xvhao= $this->generateQueueNumber($planInfo->id); - //更新主表信息 - $u_data = [ - 'list_status' => 1, - 'reservation_date' => $planInfo->date, - 'reservation_time' => $planInfo->period_id, - 'reservation_sources' => $planInfo->resources_id, - 'services_group' => $planInfo->device_id, - 'roster_id' => $planInfo->id, - 'department_id' => $planInfo->department_id, - 'xuhao' => $xvhao, - 'appointment_type_id' => $appointment_type, - 'qudao_appointment_type_id' => $appointment_type, - 'appointment_use_plan_detail'=>$appointment_use_plan_detail_arr, - 'is_emergency'=>$is_emergency - ]; - $list_all_success =true; - $offset = 0; - $u_mainList=false; - foreach($plan_qudao_tempCount as $key=>$length){ - if($length==0) continue; - $u_data['appointment_type_id']= $appointment_types[$key]; - - // 获取当前批次的记录 ID - $currentBatchIds = array_slice($mainlistids, $offset, $length);//这块逻辑得优化,之前是1个项目占位1个,现在有可能1个项目占位多个 - - foreach ($currentBatchIds as $id) { - // 为当前记录生成唯一的排队号 - $xuhao = $this->generateQueueNumber($planInfo->id); - // 更新当前记录的数据(包括 xuhao) - $u_data['xuhao'] = $xuhao; - $u_mainList = DB::table('s_list') - ->where('id', $id) - ->update($u_data); - - if (!$u_mainList) { - $list_all_success = false; - break 2; // 如果更新失败,跳出外层循环 + } + + if (!$up_plan_count_all_success && $is_emergency !== 1) { + throw new \Exception('名额不足,扣减失败'); + } + + $list_all_success = true; + foreach ($mainlistids as $id) { + $details = []; + if (isset($itemChannelMap[$id])) { + foreach ($itemChannelMap[$id] as $cId => $count) { + $details[] = [ + 'roster_detail_count_id' => $cId, + 'count' => $count + ]; } } - $offset += $length; + $xuhao = $this->generateQueueNumber($planInfo->id); + + $u_data = [ + 'list_status' => 1, + 'reservation_date' => $planInfo->date, + 'reservation_time' => $planInfo->period_id, + 'reservation_sources' => $planInfo->resources_id, + 'services_group' => $planInfo->device_id, + 'roster_id' => $planInfo->id, + 'department_id' => $planInfo->department_id, + 'xuhao' => $xuhao, + 'appointment_type_id' => $appointment_type, + 'qudao_appointment_type_id' => $appointment_type, + 'appointment_use_plan_detail' => json_encode($details), // 精确明细 + 'is_emergency' => $is_emergency + ]; + + $u_mainList = DB::table('s_list')->where('id', $id)->update($u_data); + if (!$u_mainList) { + $list_all_success = false; + break; + } } + if (!$list_all_success) { - DB::rollBack(); - return \Yz::echoError1('预约失败'); + throw new \Exception('更新医嘱状态失败'); } - $note = "预约"; + + $note = "预约"; foreach ($oldMainInfos as $key => $oldMainInfo) { if ($do_type == 2) {