From d5dd1b5e7997c2988e8a387354507dd7c16c7736 Mon Sep 17 00:00:00 2001 From: sa0ChunLuyu Date: Thu, 30 Jul 2026 02:51:12 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=AD=9B=E9=80=89=E5=85=B3?= =?UTF-8?q?=E8=81=94=E6=8E=92=E7=8F=AD=20=EF=BC=8C=20=20=20=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=E9=A2=84=E7=BA=A6=20=E5=8D=A0=E7=94=A8=E5=8F=B7?= =?UTF-8?q?=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin/YeWu/DeptCheckItemService.php | 20 +- .../Services/Admin/YeWu/PlanListService.php | 421 +++++------------- 2 files changed, 130 insertions(+), 311 deletions(-) diff --git a/Laravel/app/Services/Admin/YeWu/DeptCheckItemService.php b/Laravel/app/Services/Admin/YeWu/DeptCheckItemService.php index 4e9fa47..e00c103 100644 --- a/Laravel/app/Services/Admin/YeWu/DeptCheckItemService.php +++ b/Laravel/app/Services/Admin/YeWu/DeptCheckItemService.php @@ -32,6 +32,12 @@ public function GetItemList($searchInfo,$page,$pageSize,$userid){ ->pluck('department_number') ->toArray(); + //获取当前科室下的设备 IDs + $deptDeviceIds = DB::table('s_department_resources_device') + ->where('department_id', $user->department_id) + ->pluck('device_id') + ->toArray(); + $small_id=[]; $list=DB::table('s_check_item')->where('s_check_item.is_del', 0)->whereNotNull('sheetType')->where('sheetType', '!=', ''); //科室过滤:hisExecDepts 包含当前科室及子科室编号 @@ -62,12 +68,12 @@ public function GetItemList($searchInfo,$page,$pageSize,$userid){ } if (isset($searchInfo['deviceBind']) && $searchInfo['deviceBind'] !== '') { if ($searchInfo['deviceBind'] == 1) { - $list->whereIn('s_check_item.id', function($q) { - $q->select('item_id')->from('s_check_item_device'); + $list->whereIn('s_check_item.id', function($q) use ($deptDeviceIds) { + $q->select('item_id')->from('s_check_item_device')->whereIn('device_id', $deptDeviceIds); }); } elseif ($searchInfo['deviceBind'] == 0) { - $list->whereNotIn('s_check_item.id', function($q) { - $q->select('item_id')->from('s_check_item_device'); + $list->whereNotIn('s_check_item.id', function($q) use ($deptDeviceIds) { + $q->select('item_id')->from('s_check_item_device')->whereIn('device_id', $deptDeviceIds); }); } } @@ -93,12 +99,6 @@ public function GetItemList($searchInfo,$page,$pageSize,$userid){ //查询出所有的预约渠道 $qudaoList=DB::table('s_appointment_type')->get(); - //获取当前科室下的设备 IDs - $deptDeviceIds = DB::table('s_department_resources_device') - ->where('department_id', $user->department_id) - ->pluck('device_id') - ->toArray(); - foreach ($list as $k=>$v){ $qudao_names=[]; $qudao_ids = explode(",", $v->reservation_method); diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index 49a7f6a..c6ec407 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -1201,10 +1201,16 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme DB::beginTransaction(); try { + // 改约时提前记录旧排班 ID,在 s_list 更新后校正 + $oldRosterIds = []; if ($do_type == 2) { foreach ($oldMainInfos as $oldMainInfo) { $this->releaseSourceFromDetail($oldMainInfo->id, $oldMainInfo); + if ($oldMainInfo->roster_id) { + $oldRosterIds[] = $oldMainInfo->roster_id; + } } + $oldRosterIds = array_unique($oldRosterIds); } //查询号源渠道是否有合并 @@ -1272,10 +1278,12 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme while ($remainingInChannel > 0 && $currentItemId !== null) { $take = min($currentItemRemaining, $remainingInChannel); - if (!isset($itemChannelMap[$currentItemId][$channelId])) { - $itemChannelMap[$currentItemId][$channelId] = 0; + if ($take > 0) { + if (!isset($itemChannelMap[$currentItemId][$channelId])) { + $itemChannelMap[$currentItemId][$channelId] = 0; + } + $itemChannelMap[$currentItemId][$channelId] += $take; } - $itemChannelMap[$currentItemId][$channelId] += $take; $currentItemRemaining -= $take; $remainingInChannel -= $take; @@ -1313,10 +1321,12 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme while ($remainingEmergency > 0 && $currentItemId !== null) { $take = min($currentItemRemaining, $remainingEmergency); - if (!isset($itemChannelMap[$currentItemId][$targetChannelId])) { - $itemChannelMap[$currentItemId][$targetChannelId] = 0; + if ($take > 0) { + if (!isset($itemChannelMap[$currentItemId][$targetChannelId])) { + $itemChannelMap[$currentItemId][$targetChannelId] = 0; + } + $itemChannelMap[$currentItemId][$targetChannelId] += $take; } - $itemChannelMap[$currentItemId][$targetChannelId] += $take; $currentItemRemaining -= $take; $remainingEmergency -= $take; @@ -1452,6 +1462,23 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme } } + // 共享非持有者:使用项目的 use_seats 写入 detail,校正时自动计算总占位 + $allZero = true; + foreach ($details as $d) { + if (($d['count'] ?? 0) > 0) { $allZero = false; break; } + } + if ($allZero && isset($itemSeatsData[$id]) && !empty($itemSeatsData[$id]['is_share_slot'])) { + if ($maxShareSlotId !== null && isset($itemChannelMap[$maxShareSlotId])) { + foreach ($itemChannelMap[$maxShareSlotId] as $cId => $count) { + $details[] = [ + 'roster_detail_count_id' => $cId, + 'count' => $itemSeatsData[$id]['use_seats'] + ]; + break; + } + } + } + $xuhao = $this->generateQueueNumber($planInfo->id); $u_data = [ @@ -1509,6 +1536,13 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme ]); } + // 校正旧排班号源(s_list 已更新,旧项目已移走) + foreach ($oldRosterIds as $rid) { + $this->correctUsedCount($rid); + } + // 校正新排班号源 + $this->correctUsedCount($planInfo->id); + DB::commit(); // HIS 回写通知 @@ -1645,6 +1679,11 @@ public function CancelYuYue($MainListId, $reg_num,$do_user=null) $u_mainList = DB::table('s_list')->where(['id' => $MainListId])->update($u_data); + // 校正号源 + if ($mainInfo->roster_id) { + $this->correctUsedCount($mainInfo->roster_id); + } + $i_log = DB::table('s_list_log')->insert([ 'list_id' => $mainInfo->id, 'reg_num' => $mainInfo->reg_num, @@ -1667,302 +1706,10 @@ public function CancelYuYue($MainListId, $reg_num,$do_user=null) } } - public function releaseSourceFromDetail($mainListId, $mainInfo = null) + public function releaseSourceFromDetail($mainListId, $mainInfo = null) { - if (!$mainInfo) { - $mainInfo = DB::table('s_list')->where(['id' => $mainListId])->first(); - } - if (!$mainInfo) { - Log::error("释放号源失败-记录不存在", ['MainListId' => $mainListId]); - return false; - } - - $useDetailJson = $mainInfo->appointment_use_plan_detail; - - Log::info('[YuYueDebug] releaseSourceFromDetail 入口', [ - 'mainListId' => $mainListId, - 'entrust' => $mainInfo->entrust, - 'useDetailJson' => $useDetailJson, - 'reg_num' => $mainInfo->reg_num, - 'roster_id' => $mainInfo->roster_id, - ]); - - // 共享号源逻辑:取消号源持有者时差值释放 - $shareItem = DB::table('s_check_item') - ->where(['item_code' => $mainInfo->entrust_code, 'status' => 1, 'is_del' => 0]) - ->select('is_share_slot', 'use_seats') - ->first(); - $isShareSlot = $shareItem && !empty($shareItem->is_share_slot); - - if ($isShareSlot && !empty($useDetailJson) && $useDetailJson !== '[]') { - // 该共享项目是号源持有者:查询其他同排班同资源的共享项目 - $otherSharedItems = DB::table('s_list') - ->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name') - ->where('s_list.reg_num', $mainInfo->reg_num) - ->where('s_list.roster_id', $mainInfo->roster_id) - ->where('s_list.id', '!=', $mainListId) - ->where('s_list.list_status', 1) - ->where('s_check_item.is_share_slot', 1) - ->select('s_check_item.use_seats') - ->get(); - - if ($otherSharedItems->isNotEmpty()) { - $remainingMax = $otherSharedItems->max('use_seats'); - if ($remainingMax > 0) { - // 解析当前持有量 - $useDetails = json_decode($useDetailJson, true); - if (!is_array($useDetails)) { - return $this->releaseSourceFallback($mainInfo); - } - $currentCount = 0; - foreach ($useDetails as $d) { - $currentCount += (int)($d['count'] ?? 0); - } - - Log::info('[YuYueDebug] 共享持有者取消', [ - 'mainListId' => $mainListId, - 'entrust' => $mainInfo->entrust, - 'useDetails' => $useDetails, - 'currentCount' => $currentCount, - 'remainingMax' => $remainingMax, - 'otherSharedCount' => $otherSharedItems->count(), - ]); - - // 只释放差值 - $releaseCount = $currentCount - $remainingMax; - if ($releaseCount > 0) { - // 调整 detail 为只释放差值:从原始条目中按顺序扣除 - $adjustedDetail = []; - $remainingToRelease = $releaseCount; - foreach ($useDetails as $d) { - $entryCount = (int)($d['count'] ?? 0); - $take = min($entryCount, $remainingToRelease); - if ($take > 0) { - $adjustedDetail[] = [ - 'roster_detail_count_id' => $d['roster_detail_count_id'], - 'count' => $take - ]; - $remainingToRelease -= $take; - } - } - - // 更新剩余某个共享项目为新持有者 - // 新持有者的 detail = 原始条目中未被释放的剩余部分 - $newHolder = DB::table('s_list') - ->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name') - ->where('s_list.reg_num', $mainInfo->reg_num) - ->where('s_list.roster_id', $mainInfo->roster_id) - ->where('s_list.id', '!=', $mainListId) - ->where('s_list.list_status', 1) - ->where('s_check_item.is_share_slot', 1) - ->select('s_list.id') - ->first(); - - if ($newHolder) { - // 有可转移的共享项目:只释放差值 - $useDetailJson = json_encode($adjustedDetail); - - $newHolderDetail = []; - $adjustmentIdx = 0; - foreach ($useDetails as $d) { - $entryCount = (int)($d['count'] ?? 0); - // 该条目被释放了多少 - $releasedFromEntry = 0; - if ($adjustmentIdx < count($adjustedDetail) && $adjustedDetail[$adjustmentIdx]['roster_detail_count_id'] == $d['roster_detail_count_id']) { - $releasedFromEntry = $adjustedDetail[$adjustmentIdx]['count']; - $adjustmentIdx++; - } - $remainingInEntry = $entryCount - $releasedFromEntry; - if ($remainingInEntry > 0) { - $newHolderDetail[] = [ - 'roster_detail_count_id' => $d['roster_detail_count_id'], - 'count' => $remainingInEntry - ]; - } - } - DB::table('s_list') - ->where('id', $newHolder->id) - ->update(['appointment_use_plan_detail' => json_encode($newHolderDetail)]); - } - // 没有可转移的共享项目:不修改 useDetailJson,正常释放逻辑释放全部 - } else { - // currentCount <= remainingMax,不释放,将占位合并到新持有者 - $newHolder = DB::table('s_list') - ->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name') - ->where('s_list.reg_num', $mainInfo->reg_num) - ->where('s_list.roster_id', $mainInfo->roster_id) - ->where('s_list.id', '!=', $mainListId) - ->where('s_list.list_status', 1) - ->where('s_check_item.is_share_slot', 1) - ->select('s_list.id') - ->first(); - - Log::info('[YuYueDebug] else 转移-新持有者查询', [ - 'mainListId' => $mainListId, - 'entrust' => $mainInfo->entrust, - 'newHolderId' => $newHolder ? $newHolder->id : 'null', - ]); - - if ($newHolder) { - // 读取新持有者当前 detail,合并当前项目的占位 - $newHolderJson = DB::table('s_list')->where('id', $newHolder->id)->value('appointment_use_plan_detail'); - $newHolderDetail = json_decode($newHolderJson, true) ?: []; - - Log::info('[YuYueDebug] else 转移-合并前', [ - 'newHolderId' => $newHolder->id, - 'newHolderDetail' => $newHolderDetail, - 'cancelDetail' => $useDetails, - ]); - - foreach ($useDetails as $d) { - $found = false; - foreach ($newHolderDetail as &$nd) { - if ($nd['roster_detail_count_id'] == $d['roster_detail_count_id']) { - $nd['count'] += (int)($d['count'] ?? 0); - $found = true; - break; - } - } - if (!$found) { - $newHolderDetail[] = [ - 'roster_detail_count_id' => $d['roster_detail_count_id'], - 'count' => (int)($d['count'] ?? 0) - ]; - } - } - - Log::info('[YuYueDebug] else 转移-合并后', [ - 'newHolderId' => $newHolder->id, - 'newHolderDetail' => $newHolderDetail, - ]); - - DB::table('s_list') - ->where('id', $newHolder->id) - ->update(['appointment_use_plan_detail' => json_encode($newHolderDetail)]); - - // 更新后读回确认 - $afterUpdate = DB::table('s_list')->where('id', $newHolder->id)->value('appointment_use_plan_detail'); - Log::info('[YuYueDebug] else 转移-更新后读回', [ - 'newHolderId' => $newHolder->id, - '写入值' => json_encode($newHolderDetail), - '读回值' => $afterUpdate, - ]); - } - // 不修改 useDetailJson,但返回 true 阻止正常释放逻辑 - return true; - } - } - // remainingMax == 0:正常释放全部 - } - // 没有其他共享项目:正常释放全部 - } - - Log::info('[YuYueDebug] 进入正常释放逻辑', [ - 'mainListId' => $mainListId, - 'entrust' => $mainInfo->entrust, - 'useDetailJson' => $useDetailJson, - 'isShareSlot' => $isShareSlot ? 'true' : 'false', - ]); - - if ($isShareSlot && empty($useDetailJson)) { - // 共享项目但不是号源持有者,不释放号源 - return true; - } - - if (empty($useDetailJson)) { - $fallbackResult = $this->releaseSourceFallback($mainInfo); - return $fallbackResult; - } - - $useDetails = json_decode($useDetailJson, true); - if (!is_array($useDetails)) { - Log::error("释放号源失败-明细格式错误", ['MainListId' => $mainListId, 'detail' => $useDetailJson]); - return $this->releaseSourceFallback($mainInfo); - } - - $allSuccess = true; - foreach ($useDetails as $detail) { - $rosterDetailCountId = $detail['roster_detail_count_id'] ?? null; - $countToRelease = (int)($detail['count'] ?? 0); - - if (!$rosterDetailCountId || $countToRelease <= 0) { - continue; - } - - $countRecord = DB::table('s_source_roster_detail_count') - ->where(['id' => $rosterDetailCountId]) - ->first(); - - if (!$countRecord) { - Log::error("释放号源-记录缺失", ['MainListId' => $mainListId, 'count_id' => $rosterDetailCountId]); - $allSuccess = false; - continue; - } - - $currentVersion = $countRecord->version; - $affected = DB::table('s_source_roster_detail_count') - ->where(['id' => $rosterDetailCountId, 'version' => $currentVersion]) - ->where('used_count', '>=', $countToRelease) - ->update([ - 'used_count' => DB::raw('used_count - ' . $countToRelease), - 'version' => DB::raw('version + 1'), - 'updated_at' => date('Y-m-d H:i:s') - ]); - - Log::info('[YuYueDebug] 释放号源', [ - 'mainListId' => $mainListId, - 'entrust' => $mainInfo->entrust, - 'rosterDetailCountId' => $rosterDetailCountId, - 'countToRelease' => $countToRelease, - 'currentVersion' => $currentVersion, - 'affected' => $affected, - ]); - - if ($affected == 0) { - $retryRecord = DB::table('s_source_roster_detail_count')->where(['id' => $rosterDetailCountId])->first(); - if (!$retryRecord) { - Log::error("释放号源重试-记录缺失", ['MainListId' => $mainListId, 'count_id' => $rosterDetailCountId]); - $allSuccess = false; - continue; - } - - if ($retryRecord->used_count < $countToRelease) { - $actualRelease = $retryRecord->used_count; - if ($actualRelease > 0) { - DB::table('s_source_roster_detail_count') - ->where(['id' => $rosterDetailCountId]) - ->update([ - 'used_count' => 0, - 'version' => DB::raw('version + 1'), - 'updated_at' => date('Y-m-d H:i:s') - ]); - } - Log::warning("释放号源-数量不足", [ - 'MainListId' => $mainListId, - 'count_id' => $rosterDetailCountId, - 'expected' => $countToRelease, - 'actual' => $actualRelease - ]); - } else { - DB::table('s_source_roster_detail_count') - ->where(['id' => $rosterDetailCountId]) - ->update([ - 'used_count' => DB::raw('used_count - ' . $countToRelease), - 'version' => DB::raw('version + 1'), - 'updated_at' => date('Y-m-d H:i:s') - ]); - Log::warning("释放号源-乐观锁冲突已重试", ['MainListId' => $mainListId, 'count_id' => $rosterDetailCountId]); - } - } - } - - Log::info('[YuYueDebug] releaseSourceFromDetail 返回', [ - 'mainListId' => $mainListId, - 'entrust' => $mainInfo->entrust, - 'allSuccess' => $allSuccess ? 'true' : 'false', - ]); - - return $allSuccess; + // 由 correctUsedCount 校正替代,不再在此处释放号源 + return true; } private function releaseSourceFallback($mainInfo) @@ -1987,6 +1734,78 @@ private function releaseSourceFallback($mainInfo) return $affected > 0; } + /** + * 校正排班号源:根据项目配置(is_share_slot / use_seats)重新计算 used_count + */ + private function correctUsedCount($rosterId) + { + if (!$rosterId) return; + + // 先重置该排班所有渠道的 used_count 为 0 + DB::table('s_source_roster_detail_count') + ->where('roster_detail_id', $rosterId) + ->update(['used_count' => 0, 'version' => DB::raw('version + 1')]); + + // 获取该排班上所有活跃项目及其配置 + $activeItems = DB::table('s_list') + ->join('s_check_item', 's_list.entrust', '=', 's_check_item.item_name') + ->where('s_list.roster_id', $rosterId) + ->where('s_list.list_status', 1) + ->where('s_check_item.status', 1) + ->where('s_check_item.is_del', 0) + ->select('s_list.*', 's_check_item.use_seats', 's_check_item.is_share_slot') + ->get(); + + // 计算总占位:按人隔离,共享取 max,非共享累加 + $grouped = []; + $firstDetail = null; + + foreach ($activeItems as $item) { + $reg = $item->reg_num; + if (!isset($grouped[$reg])) { + $grouped[$reg] = ['sharedMax' => 0, 'nonSharedSum' => 0]; + } + if (!empty($item->is_share_slot)) { + $grouped[$reg]['sharedMax'] = max($grouped[$reg]['sharedMax'], (int)($item->use_seats ?? 1)); + } else { + $grouped[$reg]['nonSharedSum'] += (int)($item->use_seats ?? 1); + } + // 记录第一个有 detail 的渠道 + if ($firstDetail === null) { + $details = json_decode($item->appointment_use_plan_detail, true) ?: []; + if (!empty($details)) { + $firstDetail = $details; + } + } + } + + $totalUsed = 0; + foreach ($grouped as $reg => $g) { + $totalUsed += $g['sharedMax'] + $g['nonSharedSum']; + } + + // 按渠道分配占位 + if ($firstDetail && $totalUsed > 0) { + $totalDetailCount = array_sum(array_column($firstDetail, 'count')); + foreach ($firstDetail as $d) { + $cid = $d['roster_detail_count_id'] ?? null; + if (!$cid) continue; + $ratio = $totalDetailCount > 0 ? ($d['count'] / $totalDetailCount) : 0; + $channelUsed = (int)round($totalUsed * $ratio); + DB::table('s_source_roster_detail_count') + ->where('id', $cid) + ->update(['used_count' => $channelUsed, 'version' => DB::raw('version + 1')]); + } + } + + Log::info('[YuYueDebug] 校正号源', [ + 'rosterId' => $rosterId, + 'grouped' => $grouped, + 'totalUsed' => $totalUsed, + 'activeItemCount' => $activeItems->count(), + ]); + } + /** * 预约完成后通知 HIS 回写执行科室 * 在事务提交后调用,异常不影响主流程