bug 810 811 812 813

This commit is contained in:
Ranyunqiao
2026-06-25 10:28:23 +08:00
parent c76a165b81
commit b9ae2b877a
14 changed files with 369 additions and 43 deletions

View File

@@ -979,11 +979,29 @@ public class DocRecordAppServiceImpl implements IDocRecordAppService {
"SELECT start_time,end_time FROM adm_encounter WHERE id = ? AND patient_id = ? AND status_enum = ? AND class_enum = ?";
Object[] params = {encounterId, patientId, EncounterZyStatus.ADMITTED_TO_THE_HOSPITAL.getValue(),
EncounterClass.IMP.getValue()};
Map<String, Object> result = jdbcTemplate.queryForMap(sql, params);
HashMap<String, Date> map = new HashMap<>();
map.put("hospDate", (Timestamp)result.get("start_time"));
map.put("outTime", (Timestamp)result.get("end_time"));
return map;
try {
Map<String, Object> result = jdbcTemplate.queryForMap(sql, params);
HashMap<String, Date> map = new HashMap<>();
map.put("hospDate", (Timestamp)result.get("start_time"));
map.put("outTime", (Timestamp)result.get("end_time"));
return map;
} catch (org.springframework.dao.EmptyResultDataAccessException e) {
try {
String fallbackSql = "SELECT start_time,end_time FROM adm_encounter WHERE id = ? AND patient_id = ? AND class_enum = ?";
Object[] fallbackParams = {encounterId, patientId, EncounterClass.IMP.getValue()};
Map<String, Object> result = jdbcTemplate.queryForMap(fallbackSql, fallbackParams);
HashMap<String, Date> map = new HashMap<>();
map.put("hospDate", (Timestamp)result.get("start_time"));
map.put("outTime", (Timestamp)result.get("end_time"));
return map;
} catch (Exception ex) {
log.warn("Querying adm_encounter failed: ", ex);
HashMap<String, Date> map = new HashMap<>();
map.put("hospDate", new Date());
map.put("outTime", null);
return map;
}
}
}
/**
@@ -996,8 +1014,15 @@ public class DocRecordAppServiceImpl implements IDocRecordAppService {
String sql =
"SELECT ael.start_time FROM adm_encounter ae INNER JOIN adm_encounter_location ael ON ae.ID=ael.encounter_id AND ael.form_enum=? AND ael.status_enum=? AND ael.delete_flag='0' AND ael.tenant_id=1 LEFT JOIN adm_location al ON ael.location_id=al.ID AND al.delete_flag='0' AND al.tenant_id=1 WHERE ae.ID=? AND ae.delete_flag='0' AND ae.tenant_id=1";
Object[] params = {LocationForm.BED.getValue(), EncounterActivityStatus.ACTIVE.getValue(), encounterId};
Timestamp timestamp = jdbcTemplate.queryForObject(sql, params, Timestamp.class);
return Date.from(timestamp.toInstant());
try {
List<Timestamp> list = jdbcTemplate.queryForList(sql, Timestamp.class, params);
if (list != null && !list.isEmpty() && list.get(0) != null) {
return Date.from(list.get(0).toInstant());
}
} catch (Exception e) {
log.warn("Querying location admission date failed: ", e);
}
return new Date();
}
/**

View File

@@ -115,4 +115,13 @@ public interface IATDManageAppService {
*
*/
R<?> getPendingMedication(Long encounterId);
/**
* 退床 (取消分床)
*
* @param encounterId 住院患者id
* @return 结果
*/
R<?> cancelBedAssignment(Long encounterId);
}

View File

@@ -59,6 +59,14 @@ public interface IAdviceProcessAppService {
*/
R<?> adviceReject(List<PerformInfoDto> performInfoList);
/**
* 撤销医嘱校对
*
* @param performInfoList 医嘱信息集合
* @return 操作结果
*/
R<?> adviceCancelVerify(List<PerformInfoDto> performInfoList);
/**
* 医嘱执行
*

View File

@@ -13,6 +13,8 @@ import com.core.common.utils.DateUtils;
import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils;
import com.healthlink.his.administration.domain.Encounter;
import com.healthlink.his.administration.domain.ChargeItem;
import com.healthlink.his.administration.service.IChargeItemService;
import com.healthlink.his.administration.domain.EncounterLocation;
import com.healthlink.his.administration.domain.EncounterParticipant;
import com.healthlink.his.administration.domain.Practitioner;
@@ -115,6 +117,9 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
@Resource
private ApplicationEventPublisher eventPublisher;
@Resource
private IChargeItemService chargeItemService;
/**
* 入出转管理页面初始化
*
@@ -1002,4 +1007,85 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
docStatisticsAppService.saveOrUpdateAdmissionSigns(list);
}
}
/**
* 退床 (取消分床)
*
* @param encounterId 住院患者id
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> cancelBedAssignment(Long encounterId) {
if (encounterId == null) {
return R.fail("退床失败,请选择有效的就诊记录");
}
Encounter encounter = encounterService.getById(encounterId);
if (encounter == null) {
return R.fail("未找到该住院就诊记录");
}
// 仅已入院状态允许退床
if (!EncounterZyStatus.ADMITTED_TO_THE_HOSPITAL.getValue().equals(encounter.getStatusEnum())) {
return R.fail("该患者未在科,无法办理退床");
}
// 校验是否产生了医嘱或计费
// 1. 检查药品医嘱
long medCount = medicationRequestService.count(
new LambdaQueryWrapper<MedicationRequest>()
.eq(MedicationRequest::getEncounterId, encounterId)
.eq(MedicationRequest::getDeleteFlag, DelFlag.NO.getCode()));
if (medCount > 0) {
return R.fail("患者已产生医嘱或计费,无法直接退床");
}
// 2. 检查诊疗医嘱
long svcCount = serviceRequestService.count(
new LambdaQueryWrapper<ServiceRequest>()
.eq(ServiceRequest::getEncounterId, encounterId)
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
if (svcCount > 0) {
return R.fail("患者已产生医嘱或计费,无法直接退床");
}
// 3. 检查耗材医嘱
long devCount = deviceRequestService.count(
new LambdaQueryWrapper<DeviceRequest>()
.eq(DeviceRequest::getEncounterId, encounterId)
.eq(DeviceRequest::getDeleteFlag, DelFlag.NO.getCode()));
if (devCount > 0) {
return R.fail("患者已产生医嘱或计费,无法直接退床");
}
// 4. 检查计费记录
long chargeCount = chargeItemService.count(
new LambdaQueryWrapper<ChargeItem>()
.eq(ChargeItem::getEncounterId, encounterId));
if (chargeCount > 0) {
return R.fail("患者已产生医嘱或计费,无法直接退床");
}
// 更新原病床状态为 空闲 (LocationStatus.IDLE)
List<EncounterLocation> bedLocations = encounterLocationService.getEncounterLocationList(encounterId,
LocationForm.BED, EncounterActivityStatus.ACTIVE);
if (bedLocations != null && !bedLocations.isEmpty()) {
for (EncounterLocation bedLoc : bedLocations) {
locationService.updateStatusById(bedLoc.getLocationId(), LocationStatus.IDLE.getValue());
}
}
// 更新病床和病房就诊位置状态为已完成 (EncounterActivityStatus.COMPLETED)
// isTransfer 为 false不更新病区 WARD以保留患者的病区归属从而能继续在入科列表中显示并重新分床
encounterLocationService.updateEncounterLocationStatus(encounterId, false);
// 更新医疗参与者(住院医生、责任护士等)状态为已完成
encounterParticipantService.updateEncounterParticipantsStatus(encounterId);
// 回滚住院状态为 待入科 (EncounterZyStatus.REGISTERED)
encounter.setStatusEnum(EncounterZyStatus.REGISTERED.getValue());
encounterService.saveOrUpdateEncounter(encounter);
return R.ok("退床成功");
}
}

View File

@@ -601,6 +601,102 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
return R.ok(null, "退回成功");
}
/**
* 撤销医嘱校对
*
* @param performInfoList 医嘱信息集合
* @return 操作结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> adviceCancelVerify(List<PerformInfoDto> performInfoList) {
if (performInfoList == null || performInfoList.isEmpty()) {
return R.fail("请先选择医嘱信息");
}
// 分别创建列表来存储不同类型的请求
List<PerformInfoDto> serviceRequestList = new ArrayList<>();
List<PerformInfoDto> medRequestList = new ArrayList<>();
List<PerformInfoDto> deviceRequestList = new ArrayList<>();
for (PerformInfoDto item : performInfoList) {
if (CommonConstants.TableName.WOR_SERVICE_REQUEST.equals(item.getRequestTable())) {
serviceRequestList.add(item);
} else if (CommonConstants.TableName.MED_MEDICATION_REQUEST.equals(item.getRequestTable())) {
medRequestList.add(item);
} else if (CommonConstants.TableName.WOR_DEVICE_REQUEST.equals(item.getRequestTable())) {
deviceRequestList.add(item);
}
}
List<Long> allRequestIds = performInfoList.stream().map(PerformInfoDto::getRequestId).toList();
// 校验①:校验医嘱是否已执行。若医嘱状态已经是“已执行”,则不允许撤销校对。
List<Procedure> allProcedures = procedureService.list(
new LambdaQueryWrapper<Procedure>()
.in(Procedure::getRequestId, allRequestIds)
.eq(Procedure::getDeleteFlag, "0"));
Set<Long> executedIds = allProcedures.stream()
.filter(p -> EventStatus.COMPLETED.getValue().equals(p.getStatusEnum()))
.map(Procedure::getId)
.collect(Collectors.toSet());
Set<Long> cancelledRefundIds = allProcedures.stream()
.filter(p -> EventStatus.CANCEL.getValue().equals(p.getStatusEnum()) && p.getRefundId() != null)
.map(Procedure::getRefundId)
.collect(Collectors.toSet());
executedIds.removeAll(cancelledRefundIds);
if (!executedIds.isEmpty()) {
return R.fail("该医嘱已执行,无法撤销校对,请先去医嘱执行模块取消执行");
}
// 校验②:校验该医嘱是否已记账扣费。若已扣费,则不允许撤销校对。
List<ChargeItem> chargeItems = chargeItemService.getChargeItemInfoByReqId(allRequestIds);
boolean isBilled = chargeItems.stream().anyMatch(ci -> ChargeItemStatus.BILLED.getValue().equals(ci.getStatusEnum()));
if (isBilled) {
return R.fail("该医嘱已记账收费,若需撤销请先进行退费/计账回滚");
}
// 校验③:若为药品医嘱,校验药房是否已发药(配药)。若已发药,则不允许撤销校对。
if (!medRequestList.isEmpty()) {
List<Long> medReqIds = medRequestList.stream().map(PerformInfoDto::getRequestId).toList();
List<MedicationDispense> dispenseList = medicationDispenseService.list(
new LambdaQueryWrapper<MedicationDispense>()
.in(MedicationDispense::getMedReqId, medReqIds)
.in(MedicationDispense::getStatusEnum, Arrays.asList(
DispenseStatus.COMPLETED.getValue(),
DispenseStatus.PREPARED.getValue(),
DispenseStatus.PART_COMPLETED.getValue()
)));
if (!dispenseList.isEmpty()) {
return R.fail("药房已发药,请先进行退药申请");
}
}
// 满足所有校验,执行撤销校对(回退至“未校对”,即 ACTIVE 状态)
if (!serviceRequestList.isEmpty()) {
serviceRequestService.update(new LambdaUpdateWrapper<ServiceRequest>()
.in(ServiceRequest::getId, serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList())
.set(ServiceRequest::getStatusEnum, RequestStatus.ACTIVE.getValue())
.set(ServiceRequest::getPerformerCheckId, null)
.set(ServiceRequest::getCheckTime, null));
}
if (!medRequestList.isEmpty()) {
medicationRequestService.update(new LambdaUpdateWrapper<MedicationRequest>()
.in(MedicationRequest::getId, medRequestList.stream().map(PerformInfoDto::getRequestId).toList())
.set(MedicationRequest::getStatusEnum, RequestStatus.ACTIVE.getValue())
.set(MedicationRequest::getPerformerCheckId, null)
.set(MedicationRequest::getCheckTime, null));
}
if (!deviceRequestList.isEmpty()) {
deviceRequestService.update(new LambdaUpdateWrapper<DeviceRequest>()
.in(DeviceRequest::getId, deviceRequestList.stream().map(PerformInfoDto::getRequestId).toList())
.set(DeviceRequest::getStatusEnum, RequestStatus.ACTIVE.getValue())
.set(DeviceRequest::getPerformerCheckId, null)
.set(DeviceRequest::getCheckTime, null));
}
return R.ok(null, "撤销校对成功");
}
/**
* 医嘱执行
*

View File

@@ -166,4 +166,16 @@ public class ATDManageController {
public R<?> getPendingMedication(Long encounterId) {
return atdManageAppService.getPendingMedication(encounterId);
}
/**
* 退床 (取消分床)
*
* @param encounterId 住院患者id
* @return 结果
*/
@PutMapping(value = "/cancel-bed-assignment")
public R<?> cancelBedAssignment(Long encounterId) {
return atdManageAppService.cancelBedAssignment(encounterId);
}
}

View File

@@ -87,6 +87,17 @@ public class AdviceProcessController {
return adviceProcessAppService.adviceReject(performInfoList);
}
/**
* 撤销医嘱校对
*
* @param performInfoList 医嘱信息集合
* @return 操作结果
*/
@PutMapping(value = "/advice-cancel-verify")
public R<?> adviceCancelVerify(@RequestBody List<PerformInfoDto> performInfoList) {
return adviceProcessAppService.adviceCancelVerify(performInfoList);
}
/**
* 医嘱执行
*

View File

@@ -50,6 +50,7 @@
>
重置
</el-button>
<slot name="extra-buttons" />
<el-button
v-if="needCollapse"
link

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container">
<el-row :gutter="20">
<!--药品目录-->
@@ -207,6 +207,7 @@
:data="medicationList"
width="90%"
@checkbox-change="handleSelectionChange"
@checkbox-all="handleSelectionChange"
>
<vxe-column
type="checkbox"
@@ -648,10 +649,10 @@ function submitFileForm() {
}
/** 选择条数 */
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
function handleSelectionChange({ records }) {
ids.value = records.map((item) => item.id);
single.value = records.length != 1;
multiple.value = !records.length;
}
/** 打开新增弹窗 */

View File

@@ -146,6 +146,18 @@ export function terminalCleaning(encounterId) {
});
}
//退床 (取消分床)
export function cancelBedAssignment(encounterId) {
return request({
url: '/nurse-station/atd-manage/cancel-bed-assignment',
method: 'put',
params: {
encounterId: encounterId,
},
});
}
/**
* 获取病区列表(与病区管理页面相同的接口)
*/

View File

@@ -4,6 +4,7 @@
<PendingPatientList
:list="patientList"
:active-id="activePatientId"
id-key="encounterId"
@item-click="handleCardClick"
@item-dblclick="handleCardDblClick"
@dragstart="handleDragStart"
@@ -39,6 +40,15 @@
/>
</el-select>
</template>
<template #extra-buttons>
<el-button
type="danger"
plain
@click="handleCancelBedAssignment"
>
退床
</el-button>
</template>
</Filter>
</div>
<el-scrollbar class="right-scrollbar">
@@ -109,7 +119,7 @@ import Filter from '@/components/TableLayout/Filter.vue';
import {computed, onBeforeMount, onMounted, reactive, ref} from 'vue';
import TransferInDialog from './transferInDialog.vue';
import SignEntryDialog from './signEntryDialog.vue';
import {childLocationList, getBedInfo, getInit, getPendingInfo, getPractitionerWard} from './api';
import {childLocationList, getBedInfo, getInit, getPendingInfo, getPractitionerWard, cancelBedAssignment} from './api';
import {ElLoading, ElMessage, ElMessageBox} from 'element-plus';
import PendingPatientList from '@/components/PendingPatientList/index.vue';
@@ -161,7 +171,7 @@ const selectHoouseLoding = ref(true);
const bedStatusFilter = ref('');
const activePatientId = computed(() => {
const active = patientList.value?.find?.((it) => it?.active);
return active?.id || '';
return active?.encounterId || '';
});
const filterItems = computed(() => [
@@ -389,24 +399,11 @@ const handleTransferInOk = async () => {
await getList();
};
// 单击患者卡片事件 - 直接触发入科选床界面
// 单击患者卡片事件 - 仅高亮选中该患者
function handleCardClick(item: any, index: number) {
if (item.encounterStatus == 2) {
// 显示提示信息,指导用户如何分配床位
ElMessage({
message: '该患者尚未分配病床,请通过拖拽操作将患者分配到右侧床位',
type: 'warning',
grouping: true,
showClose: true,
});
} else {
pendingInfo.value = {
...item,
entranceType: 1,
};
transferInDialogVisible.value = true;
}
patientList.value.forEach((p) => {
p.active = p.encounterId === item.encounterId;
});
}
// 双击患者卡片事件 - 保持原有逻辑
@@ -429,6 +426,50 @@ function handleCardDblClick(item: any) {
}
}
// 退床操作 (取消分床)
const handleCancelBedAssignment = async () => {
const activePatient = patientList.value?.find?.((it) => it?.active);
if (!activePatient) {
ElMessage.warning('请先从左侧患者列表点击选中一名需要退床的已入院患者');
return;
}
if (activePatient.encounterStatus != 5) {
ElMessage.warning('选中的患者未在科,无法办理退床');
return;
}
try {
await ElMessageBox.confirm(
`确定要对患者【${activePatient.patientName || ''}】办理退床操作吗?退床后,该患者将回滚为待入科状态。`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
const loadingInstance = ElLoading.service({ fullscreen: true, text: '正在办理退床...' });
try {
const res = await cancelBedAssignment(activePatient.encounterId);
if (res.code === 200) {
ElMessage.success(res.msg || '退床成功');
// 重新加载列表数据
await getList();
} else {
ElMessage.error(res.msg || '退床失败');
}
} catch (err) {
console.error('退床失败:', err);
} finally {
loadingInstance.close();
}
} catch (cancel) {
// 用户取消了操作
}
};
// 拖拽开始事件
function handleDragStart(event: DragEvent, item: any) {
if (event.dataTransfer) {

View File

@@ -75,4 +75,15 @@ export function adviceNoExecute(data) {
method: 'put',
data: data
})
}
/**
* 撤销医嘱校对
*/
export function adviceCancelVerify(data) {
return request({
url: '/nurse-station/advice-process/advice-cancel-verify',
method: 'put',
data: data
})
}

View File

@@ -67,19 +67,13 @@
退回
</el-button>
</template>
<!-- 已校对tab显示执行/不执行 -->
<!-- 已校对tab显示撤销校对 -->
<template v-else-if="activeTab === 'verified'">
<el-button
type="success"
@click="handleExecute"
type="danger"
@click="handleCancelVerify"
>
执行
</el-button>
<el-button
type="warning"
@click="handleVoid"
>
不执行
撤销校对
</el-button>
</template>
</div>
@@ -412,7 +406,7 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from 'vue';
import {adviceVerify, cancel, adviceExecute, adviceNoExecute, getPrescriptionList} from './api';
import {adviceVerify, cancel, adviceExecute, adviceNoExecute, getPrescriptionList, adviceCancelVerify} from './api';
import {patientInfoList} from '../../components/store/patient.js';
import {formatDateStr} from '@/utils/index';
import {RequestStatus} from '@/utils/medicalConstants';
@@ -829,6 +823,23 @@ function handleVoid() {
});
}
/**
* 撤销校对
*/
function handleCancelVerify() {
let list = getSelectRows();
if (list.length === 0) {
proxy.$message.warning('请先选择医嘱信息');
return;
}
adviceCancelVerify(list).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess(res.msg);
handleGetPrescription();
}
});
}
defineExpose({
handleGetPrescription,
});

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div style="display: flex">
<el-button
type="primary"
@@ -797,6 +797,8 @@ const onSearch = (value) => {
}
.sheet {
flex: 1;
overflow: auto;
padding-bottom: 40px;
/* background: red; */
}
}