Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
wangjian963
2026-06-25 17:13:01 +08:00
9 changed files with 227 additions and 10 deletions

View File

@@ -130,5 +130,14 @@ public interface IATDManageAppService {
* @return 转科筛选选项
*/
R<?> getTransferOptions();
/**
* 换床 (指定目标床位)
*
* @param encounterId 住院患者id
* @param targetBedId 目标床位id
* @return 结果
*/
R<?> changeBedAssginment(Long encounterId, Long targetBedId);
}

View File

@@ -16,6 +16,7 @@ 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.Location;
import com.healthlink.his.administration.domain.EncounterParticipant;
import com.healthlink.his.administration.domain.Location;
import com.healthlink.his.administration.domain.Organization;
@@ -224,7 +225,7 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
public R<?> getAdmissionBedPage(AdmissionPageParam admissionPageParam, Integer pageNo, Integer pageSize) {
// 获取当前登录用户的科室 ID
Long currentUserOrgId = SecurityUtils.getLoginUser().getOrgId();
// 构建查询条件
QueryWrapper<AdmissionPageParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(admissionPageParam, null, null, null);
@@ -528,7 +529,7 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
List<EncounterParticipant> savedParticipants = encounterParticipantService.getEncounterParticipantList(encounterId);
log.info("保存后查询参与者 - encounterId: {}, 数量: {}", encounterId, savedParticipants.size());
for (EncounterParticipant ep : savedParticipants) {
log.info("参与者详情 - typeCode: {}, practitionerId: {}, statusEnum: {}",
log.info("参与者详情 - typeCode: {}, practitionerId: {}, statusEnum: {}",
ep.getTypeCode(), ep.getPractitionerId(), ep.getStatusEnum());
}
// 更新入院体征(在事务外执行,避免影响参与者数据保存)
@@ -983,7 +984,7 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
= ((List<DocStatisticsDto>) docStatisticsAppService.queryByEncounterId(encounterId).getData()).stream()
.filter(item -> DocDefinitionEnum.ADMISSION_VITAL_SIGNS.getValue().equals(item.getSource())).toList();
List<DocStatisticsDto> list = new ArrayList<>(data);
// 先删除所有已有的入院体征记录(重新保存最新数据)
for (DocStatisticsDto existingItem : data) {
if (existingItem.getId() != null) {
@@ -991,7 +992,7 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
}
}
list.clear();
map.keySet().forEach(key -> {
String value = map.get(key);
// 只保存非空值
@@ -1188,5 +1189,143 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
return R.ok("退床成功");
}
/**
* 换床
*
* @param encounterId 住院患者id
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> changeBedAssginment(Long encounterId, Long targetBedId) {
if (encounterId == null) {
return R.fail("换床失败,请选择有效的就诊记录");
}
if (targetBedId == 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("该患者未在科,无法办理换床");
}
// 查询目标床位
Location targetBed = locationService.getById(targetBedId);
if (targetBed == null) {
return R.fail("目标床位不存在");
}
if (!LocationForm.BED.getValue().equals(targetBed.getFormEnum())) {
return R.fail("所选位置不是床位");
}
// 根据目标床位的 busNo 获取其父级房间 (house)
String bedBusNo = targetBed.getBusNo();
if (bedBusNo == null || !bedBusNo.contains(".")) {
return R.fail("目标床位编码异常");
}
String[] parts = bedBusNo.split("\\.");
if (parts.length < 2) {
return R.fail("目标床位编码层级异常");
}
String houseBusNo = parts[0] + "." + parts[1];
Location targetHouse = locationService.lambdaQuery()
.eq(Location::getBusNo, houseBusNo)
.eq(Location::getFormEnum, LocationForm.HOUSE.getValue())
.eq(Location::getDeleteFlag, "0")
.one();
if (targetHouse == null) {
return R.fail("未找到目标床位所属的病房");
}
Date now = new Date();
// 检查目标床位是否已经被占用
List<EncounterLocation> occupiedBedLocs = encounterLocationService.lambdaQuery()
.eq(EncounterLocation::getLocationId, targetBedId)
.eq(EncounterLocation::getFormEnum, LocationForm.BED.getValue())
.eq(EncounterLocation::getStatusEnum, EncounterActivityStatus.ACTIVE.getValue())
.eq(EncounterLocation::getDeleteFlag, "0")
.list();
if (occupiedBedLocs != null && !occupiedBedLocs.isEmpty()) {
// Target bed is occupied! This is a bed swap (床位互换)
Long targetEncounterId = occupiedBedLocs.get(0).getEncounterId();
Encounter targetEncounter = encounterService.getById(targetEncounterId);
if (targetEncounter == null) {
return R.fail("目标床位占用患者就诊记录异常");
}
if (!EncounterZyStatus.ADMITTED_TO_THE_HOSPITAL.getValue().equals(targetEncounter.getStatusEnum())) {
return R.fail("目标床位占用患者已不在科,无法办理换床");
}
// 获取当前患者的原床位和原病房
List<EncounterLocation> currentBedLocs = encounterLocationService.getEncounterLocationList(encounterId,
LocationForm.BED, EncounterActivityStatus.ACTIVE);
if (currentBedLocs == null || currentBedLocs.isEmpty()) {
return R.fail("当前患者未分配床位,无法进行换床互换");
}
Long currentBedId = currentBedLocs.get(0).getLocationId();
List<EncounterLocation> currentHouseLocs = encounterLocationService.getEncounterLocationList(encounterId,
LocationForm.HOUSE, EncounterActivityStatus.ACTIVE);
if (currentHouseLocs == null || currentHouseLocs.isEmpty()) {
return R.fail("当前患者原病房记录不存在");
}
Long currentHouseId = currentHouseLocs.get(0).getLocationId();
// 获取被交换患者的原开始时间,保证其床位历史记录连贯性
Date targetStartTime = occupiedBedLocs.get(0).getStartTime();
if (targetStartTime == null) {
targetStartTime = now;
}
// 1. 将两位患者现有的 BED 和 HOUSE 位置状态设为 COMPLETED (false)
Integer res1 = encounterLocationService.updateEncounterLocationStatus(encounterId, false);
Integer res2 = encounterLocationService.updateEncounterLocationStatus(targetEncounterId, false);
if (res1 == 0 || res2 == 0) {
throw new RuntimeException("更新原就诊位置状态失败");
}
// 2. 为当前患者创建新位置 (目标病房和目标床位)
encounterLocationService.creatEncounterLocation(encounterId, now, targetHouse.getId(), LocationForm.HOUSE.getValue());
encounterLocationService.creatEncounterLocation(encounterId, now, targetBedId, LocationForm.BED.getValue());
// 3. 为被交换患者创建新位置 (当前患者的原病房和原床位)
encounterLocationService.creatEncounterLocation(targetEncounterId, targetStartTime, currentHouseId, LocationForm.HOUSE.getValue());
encounterLocationService.creatEncounterLocation(targetEncounterId, targetStartTime, currentBedId, LocationForm.BED.getValue());
return R.ok("床位互换成功");
} else {
// Target bed is vacant! Normal bed change
// 获取当前患者原床位
List<EncounterLocation> currentBedLocs = encounterLocationService.getEncounterLocationList(encounterId,
LocationForm.BED, EncounterActivityStatus.ACTIVE);
// 1. 将当前患者现有的 BED 和 HOUSE 位置状态设为 COMPLETED (false)
encounterLocationService.updateEncounterLocationStatus(encounterId, false);
// 2. 将原床位状态更新为空闲 (LocationStatus.IDLE)
if (currentBedLocs != null && !currentBedLocs.isEmpty()) {
for (EncounterLocation bedLoc : currentBedLocs) {
locationService.updateStatusById(bedLoc.getLocationId(), LocationStatus.IDLE.getValue());
}
}
// 3. 为当前患者创建新位置 (目标病房和目标床位)
encounterLocationService.creatEncounterLocation(encounterId, now, targetHouse.getId(), LocationForm.HOUSE.getValue());
encounterLocationService.creatEncounterLocation(encounterId, now, targetBedId, LocationForm.BED.getValue());
// 4. 将目标床位状态更新为占用 (LocationStatus.OCCUPY)
locationService.updateStatusById(targetBedId, LocationStatus.OCCUPY.getValue());
return R.ok("换床成功");
}
}
}

View File

@@ -14,6 +14,8 @@ import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* 入出转管理 controller
@@ -187,5 +189,19 @@ public class ATDManageController {
public R<?> getTransferOptions() {
return atdManageAppService.getTransferOptions();
}
/**
* 换床
*
* @param encounterId 住院患者id
* @return 结果
*/
@PutMapping(value = "/change-bed-assignment")
public R<?> changeBedAssignment(Long encounterId){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String targetBedIdStr = request.getParameter("targetBedId");
Long targetBedId = (targetBedIdStr == null || targetBedIdStr.trim().isEmpty()) ? null : Long.valueOf(targetBedIdStr);
return atdManageAppService.changeBedAssginment(encounterId, targetBedId);
}
}

View File

@@ -113,7 +113,7 @@
AND ao_target.delete_flag = '0'
WHERE ae.delete_flag = '0'
AND ae.class_enum = #{imp}
AND ae.status_enum != #{toBeRegistered}
AND ae.status_enum IN (2, 3, 5, 6)
AND ae.organization_id = #{currentUserOrgId}
GROUP BY ae.tenant_id,
ae.id,

View File

@@ -124,11 +124,14 @@ public class EncounterLocationServiceImpl extends ServiceImpl<EncounterLocationM
if (isTransfer) {
locationForms.add(LocationForm.WARD.getValue());
}
// 更新状态为已完成
// 更新状态为已完成 — 仅针对当前 ACTIVE 且未删除的记录
return baseMapper.update(null,
new LambdaUpdateWrapper<EncounterLocation>()
.set(EncounterLocation::getStatusEnum, EncounterActivityStatus.COMPLETED.getValue())
.eq(EncounterLocation::getEncounterId, encounterId).in(EncounterLocation::getFormEnum, locationForms));
.eq(EncounterLocation::getEncounterId, encounterId)
.in(EncounterLocation::getFormEnum, locationForms)
.eq(EncounterLocation::getStatusEnum, EncounterActivityStatus.ACTIVE.getValue())
.eq(EncounterLocation::getDeleteFlag, DelFlag.NO.getCode()));
}
/*

View File

@@ -65,7 +65,7 @@
"vue": "^3.5.25",
"vue-area-linkage": "^5.1.0",
"vue-cropper": "^1.1.1",
"vue-i18n": "^11.4.6",
"vue-i18n": "^9.14.5",
"vue-plugin-hiprint": "^0.0.60",
"vue-router": "^4.6.4",
"vxe-pc-ui": "^4.14.26",

View File

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

View File

@@ -41,6 +41,14 @@
</el-select>
</template>
<template #extra-buttons>
<el-button
type="primary"
plain
style="margin-right: 8px;"
@click="handleChangeBed"
>
换床
</el-button>
<el-button
type="danger"
plain
@@ -112,6 +120,11 @@
@ok-act="handleTransferInOk"
/>
<SignEntryDialog v-model:visible="signEntryDialogVisible" />
<ChangeBedDialog
v-model:visible="changeBedDialogVisible"
:bad-list="badList"
@ok-act="handleTransferInOk"
/>
</div>
</template>
<script setup lang="ts">
@@ -119,6 +132,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 ChangeBedDialog from './changeBedDialog.vue';
import {childLocationList, getBedInfo, getInit, getPendingInfo, getPractitionerWard, cancelBedAssignment} from './api';
import {ElLoading, ElMessage, ElMessageBox} from 'element-plus';
import PendingPatientList from '@/components/PendingPatientList/index.vue';
@@ -143,6 +157,7 @@ interface InitInfoOptions {
const transferInDialogVisible = ref(false);
const signEntryDialogVisible = ref(false);
const changeBedDialogVisible = ref(false);
const state = reactive({});
const loading = ref(false);
const total = ref();
@@ -426,6 +441,11 @@ function handleCardDblClick(item: any) {
}
}
// 换床操作
function handleChangeBed() {
changeBedDialogVisible.value = true;
}
// 退床操作 (取消分床)
const handleCancelBedAssignment = async () => {
const activePatient = patientList.value?.find?.((it) => it?.active);

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container">
<el-form
ref="queryRef"
@@ -129,7 +129,7 @@
<vxe-table
v-loading="loading"
:data="dataList"
height="calc(100vh - 250px)"
height="auto"
@checkbox-change="handleSelectionChange"
>
<vxe-column
@@ -388,3 +388,21 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.app-container {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
padding: 20px;
}
.vxe-table {
flex: 1;
min-height: 0;
width: 100%;
}
</style>