diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index 162a593..7b6ba27 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -1304,6 +1304,63 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme $additionalNeeded = 0; $zhanweiCount = 0; + // === HIS 回写执行科室(事务前,失败阻断预约)=== + $writebackResults = $this->preYuYueWriteback($oldMainInfos, $planInfo); + + // 全部回写失败 → 直接返回,不进事务 + if (empty($writebackResults['success'])) { + return \Yz::Return(false, 'HIS回写执行科室失败,无法完成预约', [ + 'fail_items' => $writebackResults['fail_items'], + ]); + } + + // 部分回写失败 → 过滤 mainlistids 及关联数据结构 + if (!empty($writebackResults['fail'])) { + $successIds = array_keys($writebackResults['success']); + $failIds = array_keys($writebackResults['fail']); + + // 过滤 mainlistids + $mainlistids = array_values(array_intersect($mainlistids, $successIds)); + + // 过滤 oldMainInfos + $oldMainInfos = array_filter($oldMainInfos, function($info) use ($successIds) { + return in_array($info->id, $successIds); + }); + + // 过滤 itemSeatsData + $itemSeatsData = array_intersect_key($itemSeatsData, array_flip($successIds)); + + // 重建 huchiList(剔除失败项的互斥关系) + $huchiList = []; + foreach ($oldMainInfos as $key1 => $value1) { + $cha_hc = DB::table('s_huchi')->where('is_del', 0) + ->where(function ($q) use ($value1) { + $q->where(['code1' => $value1->entrust_code])->orWhere('code2', $value1->entrust_code); + })->get(); + foreach ($cha_hc as $hc) { + $huchiList[] = [$hc->code1, $hc->code2]; + } + } + + // 重建共享号源预计算 + $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; + } + } + } + DB::beginTransaction(); try { // 改约时提前记录旧排班 ID,在 s_list 更新后校正 @@ -1715,6 +1772,12 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme 'is_emergency' => $is_emergency ]; + // 回写成功项追加执行科室字段 + if (isset($writebackResults['success'][$id]) && !empty($writebackResults['success'][$id]['newDeptCode'])) { + $u_data['RISRAcceptDeptCode'] = $writebackResults['success'][$id]['newDeptCode']; + $u_data['implement_department'] = $writebackResults['success'][$id]['newDeptName']; + } + Log::info('[YuYueDebug] 保存预约 detail', [ 'id' => $id, 'entrust' => $mainInfo->entrust ?? 'unknown', @@ -1752,7 +1815,15 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme 'new_status' => 1, 'create_user' => $do_user, 'note' => $note, - 'data' => json_encode($savedDataMap[$oldMainInfo->id] ?? []) + 'data' => json_encode(array_merge( + $savedDataMap[$oldMainInfo->id] ?? [], + isset($writebackResults['success'][$oldMainInfo->id]) ? [ + 'old_RISRAcceptDeptCode' => $writebackResults['success'][$oldMainInfo->id]['oldDeptCode'], + 'new_RISRAcceptDeptCode' => $writebackResults['success'][$oldMainInfo->id]['newDeptCode'], + 'old_implement_department' => $writebackResults['success'][$oldMainInfo->id]['oldDeptName'], + 'new_implement_department' => $writebackResults['success'][$oldMainInfo->id]['newDeptName'], + ] : [] + ), JSON_UNESCAPED_UNICODE) ]); } @@ -1765,21 +1836,14 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme DB::commit(); - // HIS 回写通知 - try { - $this->notifyHisAfterYuYue($oldMainInfos, $planInfo); - } catch (\Exception $e) { - Log::error('HIS 回写通知异常', [ - 'planid' => $planid, - 'mainlistids' => $mainlistids, - 'error' => $e->getMessage() - ]); - } - if(config('app.globals.预约完成短信通知')==1){ $this->SendMsg($oldMainInfos,$do_type); } - return \Yz::Return(true, '预约成功', ['planid' => $planid, 'mainlistids' => $mainlistids]); + return \Yz::Return(true, '预约成功', [ + 'planid' => $planid, + 'mainlistids' => $mainlistids, + 'fail_items' => $writebackResults['fail_items'] ?? [], + ]); @@ -1787,6 +1851,16 @@ public function YuYue($planid, $appointment_type, $mainlistids, $do_type,$is_eme } catch (\Exception $e) { DB::rollBack(); + + // 补偿回写:恢复 HIS 侧执行科室为旧值 + try { + $this->rollbackHisWriteback($writebackResults); + } catch (\Exception $e2) { + Log::error('补偿回写整体异常', [ + 'error' => $e2->getMessage(), + ]); + } + return \Yz::echoError1('预约异常' . $e->getMessage()); } @@ -2326,20 +2400,81 @@ public function calculateUsedCount($rosterId, $correct = 0) } /** - * 预约完成后通知 HIS 回写执行科室 - * 在事务提交后调用,异常不影响主流程 + * 预约事务前 HIS 回写执行科室 + * 逐条调用回写接口,收集成功/失败结果,不更新 s_list(事务内统一写入) + * 非正式环境默认全部成功 + * + * @param array $oldMainInfos 事务前构建的 s_list 行数组 + * @param object $planInfo 排班信息 + * @return array {success: [id => info], fail: [id => info], fail_items: [[name, reason], ...]} */ - private function notifyHisAfterYuYue($oldMainInfos, $planInfo) + private function preYuYueWriteback($oldMainInfos, $planInfo) { - // 仅内网正式环境(192.168.80.76)执行 HIS 回写 + $results = [ + 'success' => [], + 'fail' => [], + 'fail_items' => [], + ]; + + // 非正式环境:逐条随机模拟回写结果(80% 概率失败),用于测试失败链路 + // 非正式环境跳过回写,全部视为成功 if (request()->getHost() !== '192.168.80.76') { + // // [测试逻辑] 开发环境随机模拟 HIS 回写结果(80% 失败),已注释 + // Log::info('HIS 回写:当前环境非正式环境,启动随机模拟', [ + // 'host' => request()->getHost() + // ]); + // foreach ($oldMainInfos as $mainInfo) { + // $rand = mt_rand(1, 100); + // if ($rand <= 80) { + // // 模拟回写失败 + // Log::info('HIS 回写模拟失败', [ + // 'id' => $mainInfo->id, + // 'rand' => $rand, + // ]); + // $results['fail'][$mainInfo->id] = [ + // 'name' => $mainInfo->entrust, + // 'reason' => 'HIS回写执行科室失败,无法完成预约', + // ]; + // $results['fail_items'][] = [ + // 'name' => $mainInfo->entrust, + // 'reason' => 'HIS回写执行科室失败,无法完成预约', + // ]; + // } else { + // // 模拟回写成功 + // Log::info('HIS 回写模拟成功', [ + // 'id' => $mainInfo->id, + // 'rand' => $rand, + // ]); + // $results['success'][$mainInfo->id] = [ + // 'mainInfo' => $mainInfo, + // 'oldDeptCode' => '', + // 'oldDeptName' => '', + // 'newDeptCode' => '', + // 'newDeptName' => '', + // 'body' => null, + // ]; + // } + // } + // return $results; + + // [正式逻辑] 非正式环境全部视为成功 Log::info('HIS 回写跳过:当前环境非内网正式环境', [ 'host' => request()->getHost() ]); - return; + foreach ($oldMainInfos as $mainInfo) { + $results['success'][$mainInfo->id] = [ + 'mainInfo' => $mainInfo, + 'oldDeptCode' => '', + 'oldDeptName' => '', + 'newDeptCode' => '', + 'newDeptName' => '', + 'body' => null, + ]; + } + return $results; } - // 1. 获取 execDeptCode / execDeptName(本地数据库) + // 1. 获取新执行科室 $execDeptCode = ''; $execDeptName = ''; $resource = DB::table('s_department_resources') @@ -2359,30 +2494,51 @@ private function notifyHisAfterYuYue($oldMainInfos, $planInfo) } if (empty($execDeptCode)) { - Log::warning('HIS 回写通知跳过:未找到执行科室信息', [ + Log::warning('HIS 回写跳过:未找到执行科室信息', [ 'resources_id' => $planInfo->resources_id ]); - return; + // 没有执行科室信息,全部视为成功(不回写) + foreach ($oldMainInfos as $mainInfo) { + $results['success'][$mainInfo->id] = [ + 'mainInfo' => $mainInfo, + 'oldDeptCode' => $mainInfo->RISRAcceptDeptCode ?? '', + 'oldDeptName' => $mainInfo->implement_department ?? '', + 'newDeptCode' => $execDeptCode, + 'newDeptName' => $execDeptName, + 'body' => null, + ]; + } + return $results; } - // 2. 按 app_num 分组去重 + // 2. 按 app_num 分组 $grouped = []; foreach ($oldMainInfos as $mainInfo) { $appNum = $mainInfo->app_num; if (empty($appNum)) continue; if (!isset($grouped[$appNum])) { $grouped[$appNum] = [ - 'episodeid' => $mainInfo->episodeid, + 'episodeid' => $mainInfo->episodeid, 'patient_type' => $mainInfo->patient_type, - 'items' => [], + 'items' => [], ]; } $grouped[$appNum]['items'][] = $mainInfo; } if (empty($grouped)) { - Log::warning('HIS 回写通知跳过:没有有效的申请单号'); - return; + Log::warning('HIS 回写跳过:没有有效的申请单号'); + foreach ($oldMainInfos as $mainInfo) { + $results['fail'][$mainInfo->id] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + $results['fail_items'][] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + } + return $results; } // 3. 遍历每组,调 GetApplyDetail 获取申请单数据(带缓存) @@ -2394,42 +2550,69 @@ private function notifyHisAfterYuYue($oldMainInfos, $planInfo) } catch (\Exception $e) { Log::warning('HIS 查询申请单异常', [ 'app_num' => $appNum, - 'error' => $e->getMessage() + 'error' => $e->getMessage(), ]); + foreach ($group['items'] as $mainInfo) { + $results['fail'][$mainInfo->id] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + $results['fail_items'][] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + } continue; } if ($hisRes['code'] != 200 || empty($hisRes['data'])) { Log::warning('HIS 查询申请单失败', [ - 'app_num' => $appNum, - 'response' => $hisRes + 'app_num' => $appNum, + 'response' => $hisRes, ]); + foreach ($group['items'] as $mainInfo) { + $results['fail'][$mainInfo->id] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + $results['fail_items'][] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + } continue; } $hisData = $hisRes['data'][0] ?? null; if (!$hisData) { Log::warning('HIS 查询返回数据为空', ['app_num' => $appNum]); + foreach ($group['items'] as $mainInfo) { + $results['fail'][$mainInfo->id] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + $results['fail_items'][] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + } continue; } - // 4. 提取申请单级数据(comboNo 从 HIS 返回数据获取最新) + // 4. 提取申请单级数据 $comboNo = $hisData['requestNo'] ?? ''; - // 5. 遍历每条 s_list,匹配 entrust_code 获取项目级数据 + // 5. 遍历每条 s_list,匹配 entrust_code foreach ($group['items'] as $mainInfo) { - // 住院取 episodeid,门诊取 reg_num - $inpatientNo = $mainInfo->patient_type == 0 ? $mainInfo->episodeid : $mainInfo->episodeid; - - $entrustCode = $mainInfo->entrust_code; - $moOrder = ''; - $hisOrderNo = ''; + $inpatientNo = $mainInfo->episodeid; + $entrustCode = $mainInfo->entrust_code; + $moOrder = ''; + $hisOrderNo = ''; - // 在 itemList 中匹配 termCode == entrust_code if (!empty($hisData['itemList']) && is_array($hisData['itemList'])) { foreach ($hisData['itemList'] as $item) { if (($item['termCode'] ?? '') == $entrustCode) { - $moOrder = $item['orderNo'] ?? ''; + $moOrder = $item['orderNo'] ?? ''; $hisOrderNo = $item['chargeList'][0]['hisOrderNo'] ?? ''; break; } @@ -2439,65 +2622,119 @@ private function notifyHisAfterYuYue($oldMainInfos, $planInfo) if (empty($moOrder)) { Log::warning('HIS 回写:未匹配到 itemList 中的项目', [ 'entrust_code' => $entrustCode, - 'app_num' => $appNum + 'app_num' => $appNum, ]); + $results['fail'][$mainInfo->id] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + $results['fail_items'][] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; continue; } // 6. 组装并调用 HIS 回写接口 $body = [ - 'moOrder' => $moOrder, + 'moOrder' => $moOrder, 'execDeptCode' => $execDeptCode, 'execDeptName' => $execDeptName, - 'inpatientNo' => $inpatientNo, - 'hisOrderNo' => $hisOrderNo, - 'comboNo' => $comboNo, - 'itemCode' => $entrustCode, + 'inpatientNo' => $inpatientNo, + 'hisOrderNo' => $hisOrderNo, + 'comboNo' => $comboNo, + 'itemCode' => $entrustCode, ]; + // 记录旧科室 + $oldDeptCode = $mainInfo->RISRAcceptDeptCode ?? ''; + $oldDeptName = $mainInfo->implement_department ?? ''; + try { - $hisRes = HisController::Post('/roc/api/v1/qhd/common/yjyy/updateExecDept', $body); - if ($hisRes['code'] == 200) { - // 记录变更前的旧值 - $oldDeptCode = $mainInfo->RISRAcceptDeptCode ?? ''; - $oldDeptName = $mainInfo->implement_department ?? ''; - - DB::table('s_list') - ->where('id', $mainInfo->id) - ->update([ - 'RISRAcceptDeptCode' => $execDeptCode, - 'implement_department' => $execDeptName, - ]); - - DB::table('s_list_log')->insert([ - 'list_id' => $mainInfo->id, - 'reg_num' => $mainInfo->reg_num, - 'old_status' => $mainInfo->list_status, - 'new_status' => $mainInfo->list_status, - 'create_user' => 'sys_auto', - 'note' => 'HIS回写更新执行科室', - 'data' => json_encode([ - 'old_RISRAcceptDeptCode' => $oldDeptCode, - 'new_RISRAcceptDeptCode' => $execDeptCode, - 'old_implement_department' => $oldDeptName, - 'new_implement_department' => $execDeptName, - ], JSON_UNESCAPED_UNICODE), - ]); + HisController::Post('/roc/api/v1/qhd/common/yjyy/updateExecDept', $body); + // 回写成功,记录完整信息供事务内写入和补偿使用 + $results['success'][$mainInfo->id] = [ + 'mainInfo' => $mainInfo, + 'oldDeptCode' => $oldDeptCode, + 'oldDeptName' => $oldDeptName, + 'newDeptCode' => $execDeptCode, + 'newDeptName' => $execDeptName, + 'body' => $body, + 'visitSqNo' => $visitSqNo, + 'appNum' => $appNum, + 'patientType' => $group['patient_type'], + ]; - // 同步更新 ROC 缓存中对应检查项目的执行科室 - HisController::UpdateApplyDetailCache( - $visitSqNo, $appNum, $group['patient_type'], '本地', - $entrustCode, $execDeptCode, $execDeptName - ); - } + // 同步更新 ROC 缓存为新科室 + HisController::UpdateApplyDetailCache( + $visitSqNo, $appNum, $group['patient_type'], '本地', + $entrustCode, $execDeptCode, $execDeptName + ); } catch (\Exception $e) { Log::error('HIS 回写接口请求异常', [ - 'body' => $body, - 'error' => $e->getMessage() + 'body' => $body, + 'error' => $e->getMessage(), ]); + $results['fail'][$mainInfo->id] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; + $results['fail_items'][] = [ + 'name' => $mainInfo->entrust, + 'reason' => 'HIS回写执行科室失败,无法完成预约', + ]; } } } + + return $results; + } + + /** + * 补偿回写:事务回滚后恢复 HIS 侧执行科室为旧值 + * 仅处理回写成功且新旧科室不一致的项目 + */ + private function rollbackHisWriteback($writebackResults) + { + // 非正式环境跳过补偿 + if (request()->getHost() !== '192.168.80.76') { + return; + } + + foreach ($writebackResults['success'] as $id => $info) { + if (empty($info['body'])) continue; + + // 新旧科室一致,无需补偿 + if ($info['newDeptCode'] === $info['oldDeptCode']) { + continue; + } + + // 复用 body,替换为旧科室 + $rollbackBody = $info['body']; + $rollbackBody['execDeptCode'] = $info['oldDeptCode']; + $rollbackBody['execDeptName'] = $info['oldDeptName']; + + try { + HisController::Post('/roc/api/v1/qhd/common/yjyy/updateExecDept', $rollbackBody); + + // 同步更新 ROC 缓存为旧科室 + HisController::UpdateApplyDetailCache( + $info['visitSqNo'], + $info['appNum'], + $info['patientType'], + '本地', + $info['body']['itemCode'], + $info['oldDeptCode'], + $info['oldDeptName'] + ); + } catch (\Exception $e) { + Log::error('补偿回写HIS失败', [ + 'id' => $id, + 'oldDeptCode' => $info['oldDeptCode'], + 'error' => $e->getMessage(), + ]); + } + } } //短信提醒 diff --git a/YiJi-admin/src/components/Yewu/YuYue202506.vue b/YiJi-admin/src/components/Yewu/YuYue202506.vue index e545f9b..d9a1b04 100644 --- a/YiJi-admin/src/components/Yewu/YuYue202506.vue +++ b/YiJi-admin/src/components/Yewu/YuYue202506.vue @@ -1598,23 +1598,46 @@ let TanChuangMsgDialogVisible=ref(false) } } }, true) - } else { - ElMessageBox.confirm( - res.msg, - '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'error', - showCancelButton: false - } - ) - .then(() => { + // HIS 回写失败项弹窗 + if (res.data?.fail_items?.length > 0) { + let html = '' + html += '' + res.data.fail_items.forEach((f, i) => { + html += '' + }) + html += '
序号项目名称结果
' + (i + 1) + '' + f.name + '' + f.reason + '
' + resultDialogHtml.value = html + resultDialogVisible.value = true + } + } else { + // 如果有回写失败明细,用结果弹窗展示 + if (res.data?.fail_items?.length > 0) { + let html = '' + html += '' + res.data.fail_items.forEach((f, i) => { + html += '' + }) + html += '
序号项目名称结果
' + (i + 1) + '' + f.name + '' + f.reason + '
' + resultDialogHtml.value = html + resultDialogVisible.value = true + } else { + ElMessageBox.confirm( + res.msg, + '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'error', + showCancelButton: false + } + ) + .then(() => { - }) - .catch(() => { + }) + .catch(() => { - }) - } + }) + } + } }) return } @@ -1638,7 +1661,9 @@ let TanChuangMsgDialogVisible=ref(false) is_emergency: type == 111 ? 1 : null }) if (res.status) { - ids.forEach(id => { + // 仅遍历后端返回的实际预约成功项,避免回写失败的项目被误加入 successItems + const bookedIds = res.data?.mainlistids || ids + bookedIds.forEach(id => { const row = entrustTableDate.value.find(v => v.id === id) if (row) { const timeStr = (row.period_begin_time ? row.period_begin_time.substring(0, 5) : '') + '-' + (row.period_end_time ? row.period_end_time.substring(0, 5) : '') @@ -1646,6 +1671,12 @@ let TanChuangMsgDialogVisible=ref(false) successEntrustIds.push(row.entrust_id) } }) + // 追加 HIS 回写失败项 + if (res.data?.fail_items?.length > 0) { + res.data.fail_items.forEach(f => { + failItems.push({ name: f.name, msg: f.reason }) + }) + } } else { ids.forEach(id => { const row = entrustTableDate.value.find(v => v.id === id)