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 += '| ' + (i + 1) + ' | ' + f.name + ' | ' + f.reason + ' |
'
+ })
+ html += '
'
+ 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 += '| ' + (i + 1) + ' | ' + f.name + ' | ' + f.reason + ' |
'
+ })
+ html += '
'
+ 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)