|
|
|
|
@ -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) {
|
|
|
|
|
|