Compare commits
19 Commits
317d5d06d8
...
a24abd4f68
| Author | SHA1 | Date | |
|---|---|---|---|
| a24abd4f68 | |||
| e2716e6656 | |||
| b1e7d45416 | |||
| 46053a3c73 | |||
| 568b10829c | |||
| 4b54b04bc1 | |||
| 6a2a579fec | |||
| 09dbd5e9f0 | |||
| d40514b184 | |||
| 3fd259b3ea | |||
| 690f680866 | |||
| 0bb113557c | |||
| 359afba793 | |||
| 424a99f412 | |||
| 9cfb7fcf78 | |||
| d86614982a | |||
| 10cca41375 | |||
|
|
b82d9774f2 | ||
| 1a7092d2d8 |
@@ -152,6 +152,9 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
|
||||
InspectionLabApply inspectionLabApply = new InspectionLabApply();
|
||||
//将 dto 数据复制到 InspectionLabApply 对象中
|
||||
BeanUtils.copyProperties(doctorStationLabApplyDto, inspectionLabApply);
|
||||
// 修复:applicationId 与 id 字段名不一致,BeanUtils 不会自动拷贝,需手动设置
|
||||
// 否则 saveOrUpdate 永远走 INSERT,导致编辑保存时主键冲突
|
||||
inspectionLabApply.setId(doctorStationLabApplyDto.getApplicationId());
|
||||
//设置租户 ID
|
||||
inspectionLabApply.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||
//获取当前登陆用户名称
|
||||
@@ -337,8 +340,11 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
|
||||
}
|
||||
adviceSaveDto.setAccountId(accountId);
|
||||
|
||||
// 将申请单号作为业务关联标识(请求内容 json)
|
||||
adviceSaveDto.setContentJson("{\"applyNo\":\"" + doctorStationLabApplyDto.getApplyNo() + "\"}");
|
||||
// 将申请单号和项目名称作为业务关联标识(请求内容 json)
|
||||
// 项目名称用于读取时 COALESCE 回退显示(检验项目存的是 lab_activity_definition 的 ID,
|
||||
// 读取 SQL JOIN 的是 wor_activity_definition,会匹配不上,所以需要在 contentJson 中冗余存储名称)
|
||||
String escapedItemName = itemName.replace("\"", "\\\"");
|
||||
adviceSaveDto.setContentJson("{\"applyNo\":\"" + doctorStationLabApplyDto.getApplyNo() + "\",\"adviceName\":\"" + escapedItemName + "\"}");
|
||||
|
||||
// 设置其他必要字段
|
||||
// 请求数量
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.healthlink.his.web.infection.appservice;
|
||||
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ICdssAppService {
|
||||
Map<String, Object> evaluateRules(Long encounterId);
|
||||
List<CdssAlert> getAlerts(Long encounterId);
|
||||
boolean acknowledgeAlert(Long alertId);
|
||||
List<CdssRule> getRules(Map<String, Object> params);
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package com.healthlink.his.web.infection.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import com.healthlink.his.infection.service.ICdssAlertService;
|
||||
import com.healthlink.his.infection.service.ICdssRuleService;
|
||||
import com.healthlink.his.web.infection.appservice.ICdssAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class CdssAppServiceImpl implements ICdssAppService {
|
||||
|
||||
private final ICdssRuleService cdssRuleService;
|
||||
private final ICdssAlertService cdssAlertService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> evaluateRules(Long encounterId) {
|
||||
log.info("CDSS规则评估开始, encounterId={}", encounterId);
|
||||
|
||||
List<CdssRule> enabledRules = cdssRuleService.list(
|
||||
new LambdaQueryWrapper<CdssRule>()
|
||||
.eq(CdssRule::getEnabled, true)
|
||||
);
|
||||
|
||||
List<CdssAlert> newAlerts = new ArrayList<>();
|
||||
for (CdssRule rule : enabledRules) {
|
||||
CdssAlert alert = new CdssAlert();
|
||||
alert.setEncounterId(encounterId);
|
||||
alert.setPatientId(0L);
|
||||
alert.setRuleId(rule.getId());
|
||||
alert.setAlertType(rule.getRuleType());
|
||||
alert.setAlertMessage("[" + rule.getRuleName() + "] " + rule.getSuggestion());
|
||||
alert.setSeverity(rule.getSeverity());
|
||||
alert.setAcknowledged(false);
|
||||
cdssAlertService.save(alert);
|
||||
newAlerts.add(alert);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalRules", enabledRules.size());
|
||||
result.put("newAlertCount", newAlerts.size());
|
||||
result.put("newAlerts", newAlerts);
|
||||
result.put("evaluateTime", new Date());
|
||||
|
||||
log.info("CDSS规则评估完成: {}条规则, 生成{}条告警", enabledRules.size(), newAlerts.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CdssAlert> getAlerts(Long encounterId) {
|
||||
LambdaQueryWrapper<CdssAlert> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CdssAlert::getEncounterId, encounterId);
|
||||
wrapper.orderByDesc(CdssAlert::getCreateTime);
|
||||
return cdssAlertService.list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean acknowledgeAlert(Long alertId) {
|
||||
CdssAlert alert = cdssAlertService.getById(alertId);
|
||||
if (alert == null) {
|
||||
return false;
|
||||
}
|
||||
alert.setAcknowledged(true);
|
||||
alert.setAcknowledgedTime(new Date());
|
||||
return cdssAlertService.updateById(alert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CdssRule> getRules(Map<String, Object> params) {
|
||||
LambdaQueryWrapper<CdssRule> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
String ruleType = getStr(params, "ruleType");
|
||||
if (ruleType != null && !ruleType.isEmpty()) {
|
||||
wrapper.eq(CdssRule::getRuleType, ruleType);
|
||||
}
|
||||
String severity = getStr(params, "severity");
|
||||
if (severity != null && !severity.isEmpty()) {
|
||||
wrapper.eq(CdssRule::getSeverity, severity);
|
||||
}
|
||||
String keyword = getStr(params, "keyword");
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(CdssRule::getRuleName, keyword)
|
||||
.or().like(CdssRule::getRuleCode, keyword));
|
||||
}
|
||||
wrapper.orderByDesc(CdssRule::getCreateTime);
|
||||
return cdssRuleService.list(wrapper);
|
||||
}
|
||||
|
||||
private String getStr(Map<String, Object> params, String key) {
|
||||
Object v = params.get(key);
|
||||
return v != null ? v.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.healthlink.his.web.infection.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import com.healthlink.his.web.infection.appservice.ICdssAppService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "CDSS临床决策支持")
|
||||
@RestController
|
||||
@RequestMapping("/infection/cdss")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class CdssController {
|
||||
|
||||
private final ICdssAppService cdssAppService;
|
||||
|
||||
@Operation(summary = "评估规则生成告警")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:edit')")
|
||||
@PostMapping("/evaluate")
|
||||
public R<?> evaluateRules(@RequestParam Long encounterId) {
|
||||
log.info("CDSS规则评估, encounterId={}", encounterId);
|
||||
return R.ok(cdssAppService.evaluateRules(encounterId));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取告警列表")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
|
||||
@GetMapping("/alerts/{encounterId}")
|
||||
public R<?> getAlerts(@PathVariable Long encounterId) {
|
||||
return R.ok(cdssAppService.getAlerts(encounterId));
|
||||
}
|
||||
|
||||
@Operation(summary = "确认告警")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:edit')")
|
||||
@PostMapping("/alerts/{id}/acknowledge")
|
||||
public R<?> acknowledgeAlert(@PathVariable Long id) {
|
||||
return R.ok(cdssAppService.acknowledgeAlert(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询规则列表")
|
||||
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
|
||||
@GetMapping("/rules")
|
||||
public R<?> getRules(
|
||||
@RequestParam(value = "ruleType", required = false) String ruleType,
|
||||
@RequestParam(value = "severity", required = false) String severity,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
Map<String, Object> params = new java.util.HashMap<>();
|
||||
if (ruleType != null) params.put("ruleType", ruleType);
|
||||
if (severity != null) params.put("severity", severity);
|
||||
if (keyword != null) params.put("keyword", keyword);
|
||||
return R.ok(cdssAppService.getRules(params));
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,9 @@ import com.healthlink.his.web.document.dto.DocStatisticsDto;
|
||||
import com.healthlink.his.web.inhospitalnursestation.appservice.IATDManageAppService;
|
||||
import com.healthlink.his.web.inhospitalnursestation.dto.*;
|
||||
import com.healthlink.his.web.inhospitalnursestation.mapper.ATDManageAppMapper;
|
||||
import com.healthlink.his.workflow.domain.DeviceRequest;
|
||||
import com.healthlink.his.workflow.domain.ServiceRequest;
|
||||
import com.healthlink.his.workflow.service.IDeviceRequestService;
|
||||
import com.healthlink.his.workflow.service.IServiceRequestService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -98,6 +100,9 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
||||
@Resource
|
||||
private IServiceRequestService serviceRequestService;
|
||||
|
||||
@Resource
|
||||
private IDeviceRequestService deviceRequestService;
|
||||
|
||||
@Resource
|
||||
private IPractitionerService practitionerService;
|
||||
|
||||
@@ -402,7 +407,8 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
||||
ChargeItemStatus.BILLABLE.getValue(), ChargeItemStatus.BILLED.getValue(),
|
||||
ChargeItemStatus.REFUNDED.getValue(), EncounterClass.IMP.getValue(),
|
||||
GenerateSource.DOCTOR_PRESCRIPTION.getValue(), ActivityDefCategory.TRANSFER.getCode(),
|
||||
ActivityDefCategory.DISCHARGE.getCode(), ActivityDefCategory.NURSING.getCode());
|
||||
ActivityDefCategory.DISCHARGE.getCode(), ActivityDefCategory.NURSING.getCode(),
|
||||
RequestStatus.DISPENSE_COMPLETED.getValue());
|
||||
inpatientAdvicePage.getRecords().forEach(e -> {
|
||||
// 是否皮试
|
||||
e.setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getSkinTestFlag()));
|
||||
@@ -622,20 +628,10 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
||||
if (encounterId == null) {
|
||||
return R.fail("转科失败,请选择有效的患者");
|
||||
}
|
||||
// 获取是否还有待执行医嘱
|
||||
List<MedicationRequest> medicationRequestList = medicationRequestService
|
||||
.list(new LambdaQueryWrapper<MedicationRequest>().eq(MedicationRequest::getEncounterId, encounterId)
|
||||
.ne(MedicationRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
|
||||
.eq(MedicationRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
List<ServiceRequest> serviceRequestList = serviceRequestService
|
||||
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getEncounterId, encounterId)
|
||||
.ne(ServiceRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.TRANSFER.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.DISCHARGE.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.NURSING.getValue())
|
||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
if (!medicationRequestList.isEmpty() || !serviceRequestList.isEmpty()) {
|
||||
return R.fail("有待执行的医嘱,请执行完后再转科");
|
||||
// 校验是否有未处理完的医嘱(药品/诊疗/耗材)
|
||||
String blockingMsg = checkBlockingOrders(encounterId);
|
||||
if (blockingMsg != null) {
|
||||
return R.fail(blockingMsg);
|
||||
}
|
||||
// 查询患者待取的药品
|
||||
List<MedicationDispense> medicationDispenseList = medicationDispenseService
|
||||
@@ -686,25 +682,15 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> hospitalDischarge(Long encounterId) {
|
||||
if (encounterId == null) {
|
||||
return R.fail("出院失败,请选择有效的患者");
|
||||
}
|
||||
// 获取是否还有待执行医嘱
|
||||
List<MedicationRequest> medicationRequestList = medicationRequestService
|
||||
.list(new LambdaQueryWrapper<MedicationRequest>().eq(MedicationRequest::getEncounterId, encounterId)
|
||||
.ne(MedicationRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
|
||||
.eq(MedicationRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
List<ServiceRequest> serviceRequestList = serviceRequestService
|
||||
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getEncounterId, encounterId)
|
||||
.ne(ServiceRequest::getStatusEnum, RequestStatus.STOPPED.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.TRANSFER.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.DISCHARGE.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.NURSING.getValue())
|
||||
.eq(ServiceRequest::getParentId, null)
|
||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
if (!medicationRequestList.isEmpty() || !serviceRequestList.isEmpty()) {
|
||||
return R.fail("有待执行的医嘱,请执行完后再出院");
|
||||
// 校验是否有未处理完的医嘱(药品/诊疗/耗材)
|
||||
String blockingMsg = checkBlockingOrders(encounterId);
|
||||
if (blockingMsg != null) {
|
||||
return R.fail(blockingMsg);
|
||||
}
|
||||
// 查询患者待取的药品
|
||||
List<MedicationDispense> medicationDispenseList = medicationDispenseService
|
||||
@@ -737,6 +723,15 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
||||
|| EncounterZyStatus.REGISTERED.getValue().equals(encounter.getStatusEnum())) {
|
||||
return R.fail("请等待出院结算完成后再清床");
|
||||
}
|
||||
// 待转科患者应使用转科功能,不允许直接清床
|
||||
if (EncounterZyStatus.PENDING_TRANSFER.getValue().equals(encounter.getStatusEnum())) {
|
||||
return R.fail("患者处于待转科状态,请使用转科功能");
|
||||
}
|
||||
// 校验是否有未处理完的医嘱(药品/诊疗/耗材)
|
||||
String blockingMsg = checkBlockingOrders(encounterId);
|
||||
if (blockingMsg != null) {
|
||||
return R.fail(blockingMsg);
|
||||
}
|
||||
// 更新患者位置状态:已完成
|
||||
encounterLocationService.updateEncounterLocationStatus(encounterId, true);
|
||||
// 更新患者相关医生状态:已完成
|
||||
@@ -751,6 +746,73 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
||||
return R.ok("清床完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查患者是否有未处理完的医嘱(药品/诊疗/耗材),用于转科/出院/清床前的统一校验。
|
||||
* <p>
|
||||
* 使用正向白名单方式,仅查询处于"阻塞状态"的医嘱:
|
||||
* - DRAFT(1) 待发送 —— 医生已开嘱尚未提交
|
||||
* - ACTIVE(2) 已发送 —— 待护士校对
|
||||
* - COMPLETED(3) 已校对 —— 护士校对通过但尚未执行完
|
||||
* - CHECK_VERIFIED(10) 检查已校对 —— 检查类医嘱校对通过,待执行
|
||||
* - PENDING_STOP(13) 停嘱待核对 —— 医生已停嘱,待护士核对
|
||||
* <p>
|
||||
* 以下状态不会阻塞(已走完流程或已取消):
|
||||
* - STOPPED(6) 停嘱
|
||||
* - CANCELLED(5) 取消/待退
|
||||
* - PENDING_RECEIVE(11) 待接收 —— 检查已送检
|
||||
* - CHECK_RECEIVED(12) 已接收 —— 医技已接单
|
||||
* - DISPENSE_COMPLETED(20) 发药完成
|
||||
*
|
||||
* @param encounterId 住院患者id
|
||||
* @return 错误提示消息,null 表示无阻塞
|
||||
*/
|
||||
private String checkBlockingOrders(Long encounterId) {
|
||||
// 阻塞状态白名单:仅这些状态的医嘱会阻止转科/出院/清床
|
||||
List<Integer> blockingStatuses = List.of(
|
||||
RequestStatus.DRAFT.getValue(),
|
||||
RequestStatus.ACTIVE.getValue(),
|
||||
RequestStatus.COMPLETED.getValue(),
|
||||
RequestStatus.CHECK_VERIFIED.getValue(),
|
||||
RequestStatus.PENDING_STOP.getValue()
|
||||
);
|
||||
|
||||
// 1. 检查药品医嘱(MedicationRequest)
|
||||
long medCount = medicationRequestService.count(
|
||||
new LambdaQueryWrapper<MedicationRequest>()
|
||||
.eq(MedicationRequest::getEncounterId, encounterId)
|
||||
.in(MedicationRequest::getStatusEnum, blockingStatuses)
|
||||
.eq(MedicationRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
if (medCount > 0) {
|
||||
return "有未处理完的医嘱,请先处理完再操作";
|
||||
}
|
||||
|
||||
// 2. 检查诊疗医嘱(ServiceRequest),排除转科/出院/护理级别类医嘱,只查父级医嘱
|
||||
long svcCount = serviceRequestService.count(
|
||||
new LambdaQueryWrapper<ServiceRequest>()
|
||||
.eq(ServiceRequest::getEncounterId, encounterId)
|
||||
.in(ServiceRequest::getStatusEnum, blockingStatuses)
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.TRANSFER.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.DISCHARGE.getValue())
|
||||
.ne(ServiceRequest::getCategoryEnum, ActivityDefCategory.NURSING.getValue())
|
||||
.eq(ServiceRequest::getParentId, null)
|
||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
if (svcCount > 0) {
|
||||
return "有未处理完的医嘱,请先处理完再操作";
|
||||
}
|
||||
|
||||
// 3. 检查耗材医嘱(DeviceRequest)
|
||||
long devCount = deviceRequestService.count(
|
||||
new LambdaQueryWrapper<DeviceRequest>()
|
||||
.eq(DeviceRequest::getEncounterId, encounterId)
|
||||
.in(DeviceRequest::getStatusEnum, blockingStatuses)
|
||||
.eq(DeviceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||
if (devCount > 0) {
|
||||
return "有未处理完的医嘱,请先处理完再操作";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊断个人账户金额信息获取
|
||||
*
|
||||
|
||||
@@ -461,15 +461,17 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
||||
}
|
||||
// 处理转科/出院等特殊医嘱
|
||||
for (ServiceRequest serviceRequest : allServiceRequests) {
|
||||
// Bug #718: 延迟状态变更时机。核对通过时不立即变更患者就诊状态,
|
||||
// 而是等到护士在【入出转管理】中手动点击“转科”或“清床”时再处理。
|
||||
// 这样可以确保护士在真正转出前,依然能在在科列表中选中患者并处理遗留医嘱。
|
||||
// 核对通过后更新就诊状态,使患者出现在对应的管理列表中:
|
||||
// - 转科医嘱 → 状态6(待转科),患者出现在【入出转管理→转出】列表
|
||||
// - 出院医嘱 → 状态3(待出院),患者出现在【入出转管理→出院】列表
|
||||
// 注意:此处仅更新 adm_encounter.status_enum,不释放床位。
|
||||
// 床位释放发生在护士手动点击”转科”/”出院”时的 transferDepartment()/hospitalDischarge() 中。
|
||||
if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) {
|
||||
// encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
||||
// EncounterZyStatus.PENDING_TRANSFER.getValue());
|
||||
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
||||
EncounterZyStatus.PENDING_TRANSFER.getValue());
|
||||
} else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) {
|
||||
// encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
||||
// EncounterZyStatus.AWAITING_DISCHARGE.getValue());
|
||||
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
||||
EncounterZyStatus.AWAITING_DISCHARGE.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,10 +153,11 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
|
||||
// 就诊ID集合
|
||||
String encounterIds = dispenseFormSearchParam.getEncounterIds();
|
||||
dispenseFormSearchParam.setEncounterIds(null);
|
||||
// 汇总单查询不适用的字段清空(汇总单表无 tcm_flag 等列,避免 SQL 报错)
|
||||
// 汇总单查询不适用的字段清空(汇总单表无 tcm_flag、summary_status 等列,避免 SQL 报错)
|
||||
dispenseFormSearchParam.setTcmFlag(null);
|
||||
dispenseFormSearchParam.setTherapyEnum(null);
|
||||
dispenseFormSearchParam.setExeTime(null);
|
||||
dispenseFormSearchParam.setSummaryStatus(null);
|
||||
|
||||
// 构建查询条件
|
||||
QueryWrapper<DispenseFormSearchParam> queryWrapper = HisQueryUtils.buildQueryWrapper(dispenseFormSearchParam,
|
||||
@@ -171,7 +172,7 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
|
||||
|
||||
// 汇总单分页列表
|
||||
Page<MedicineSummaryFormDto> medicineSummaryFormPage = medicineSummaryAppMapper.selectMedicineSummaryFormPage(
|
||||
new Page<>(pageNo, pageSize), queryWrapper, DispenseStatus.PREPARATION.getValue(),
|
||||
new Page<>(pageNo, pageSize), queryWrapper,
|
||||
SupplyType.SUMMARY_DISPENSE.getValue());
|
||||
medicineSummaryFormPage.getRecords().forEach(e -> {
|
||||
// 发药状态(汇总单展示文案)
|
||||
|
||||
@@ -144,7 +144,8 @@ public interface ATDManageAppMapper {
|
||||
@Param("admittingDoctor") String admittingDoctor, @Param("personalCashAccount") String personalCashAccount,
|
||||
@Param("billable") Integer billable, @Param("billed") Integer billed, @Param("refunded") Integer refunded,
|
||||
@Param("imp") Integer imp, @Param("doctorPrescription") Integer doctorPrescription,
|
||||
@Param("transfer") String transfer, @Param("discharge") String discharge, @Param("nursing") String nursing);
|
||||
@Param("transfer") String transfer, @Param("discharge") String discharge, @Param("nursing") String nursing,
|
||||
@Param("dispenseCompleted") Integer dispenseCompleted);
|
||||
|
||||
/**
|
||||
* 查询执行记录
|
||||
|
||||
@@ -46,13 +46,11 @@ public interface MedicineSummaryAppMapper {
|
||||
*
|
||||
* @param page 分页信息
|
||||
* @param queryWrapper 查询条件
|
||||
* @param preparation 发药状态:待配药
|
||||
* @param summaryDispense 单据类型:汇总发药
|
||||
* @return 汇总单列表
|
||||
*/
|
||||
Page<MedicineSummaryFormDto> selectMedicineSummaryFormPage(@Param("page") Page<MedicineSummaryFormDto> page,
|
||||
@Param(Constants.WRAPPER) QueryWrapper<DispenseFormSearchParam> queryWrapper,
|
||||
@Param("preparation") Integer preparation,
|
||||
@Param("summaryDispense") Integer summaryDispense);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
package com.healthlink.his.web.mrhomepage.controller;
|
||||
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.mrhomepage.domain.MrDrgGrouping;
|
||||
import com.healthlink.his.mrhomepage.service.IMrDrgGroupingService;
|
||||
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.math.BigDecimal;import java.util.*;
|
||||
@RestController @RequestMapping("/drg-analysis") @Slf4j @AllArgsConstructor
|
||||
public class DrgAnalysisController {
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/mr-homepage/drg")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class MrHomepageDrgController {
|
||||
|
||||
private final IMrDrgGroupingService drgService;
|
||||
|
||||
@GetMapping("/cost-efficiency")
|
||||
public R<?> getCostEfficiency() {
|
||||
List<MrDrgGrouping> all = drgService.list();
|
||||
BigDecimal totalCost = BigDecimal.ZERO; int count = 0;
|
||||
BigDecimal totalCost = BigDecimal.ZERO;
|
||||
int count = 0;
|
||||
for (MrDrgGrouping g : all) {
|
||||
if (g.getTotalCost() != null) { totalCost = totalCost.add(g.getTotalCost()); count++; }
|
||||
if (g.getTotalCost() != null) {
|
||||
totalCost = totalCost.add(g.getTotalCost());
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("avgCost", count > 0 ? totalCost.divide(BigDecimal.valueOf(count), 2, BigDecimal.ROUND_HALF_UP) : BigDecimal.ZERO);
|
||||
@@ -174,7 +174,12 @@ public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService
|
||||
|
||||
private Map<String, Object> getUnclosedWarnings(Integer pageNum, Integer pageSize) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<Map<String, Object>> rows = recordMapper.selectUnclosedWarnings();
|
||||
// 使用分页查询避免全量拉取导致卡死
|
||||
int pn = (pageNum != null && pageNum > 0) ? pageNum : 1;
|
||||
int ps = (pageSize != null && pageSize > 0) ? pageSize : 10;
|
||||
int offset = (pn - 1) * ps;
|
||||
long total = recordMapper.countUnclosedWarnings();
|
||||
List<Map<String, Object>> rows = recordMapper.selectUnclosedWarningsPaged(ps, offset);
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
List<Map<String, Object>> warnings = new ArrayList<>();
|
||||
for (Map<String, Object> row : rows) {
|
||||
@@ -195,6 +200,9 @@ public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService
|
||||
warnings.add(warning);
|
||||
}
|
||||
result.put("records", warnings);
|
||||
result.put("total", total);
|
||||
result.put("pageNum", pn);
|
||||
result.put("pageSize", ps);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
@@ -351,6 +352,30 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
|
||||
}
|
||||
// 退药更新
|
||||
medicationDispenseService.updateBatchById(refundMedList);
|
||||
|
||||
// 退药完成后,检查医嘱关联的发药记录是否全部退完,如果是则回写医嘱状态为已停止
|
||||
// 解决:医嘱取消(status=5)后退药完成,医嘱状态未变更,导致护士站"待处理执行单"仍显示已退完的医嘱
|
||||
Set<Long> distinctMedReqIds = refundMedList.stream()
|
||||
.map(MedicationDispense::getMedReqId).collect(Collectors.toSet());
|
||||
for (Long medReqId : distinctMedReqIds) {
|
||||
// 查询该医嘱下所有发药记录
|
||||
List<MedicationDispense> allDispenses = medicationDispenseService.list(
|
||||
new LambdaQueryWrapper<MedicationDispense>()
|
||||
.eq(MedicationDispense::getMedReqId, medReqId)
|
||||
.eq(MedicationDispense::getDeleteFlag, "0"));
|
||||
// 判断是否全部已退药
|
||||
boolean allRefunded = allDispenses.stream()
|
||||
.allMatch(d -> DispenseStatus.REFUNDED.getValue().equals(d.getStatusEnum()));
|
||||
if (allRefunded) {
|
||||
// 回写医嘱状态:取消/待退(5) → 已停止(6)
|
||||
// STOPPED(6)会被护士站查询排除,不再出现在"待处理执行单"中
|
||||
medicationRequestService.update(
|
||||
new LambdaUpdateWrapper<MedicationRequest>()
|
||||
.eq(MedicationRequest::getId, medReqId)
|
||||
.eq(MedicationRequest::getStatusEnum, RequestStatus.CANCELLED.getValue())
|
||||
.set(MedicationRequest::getStatusEnum, RequestStatus.STOPPED.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理退耗材
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.healthlink.his.web.quality.appservice.impl;
|
||||
import com.healthlink.his.quality.domain.EmrDefect;
|
||||
import com.healthlink.his.quality.domain.EmrQualityScore;
|
||||
import com.healthlink.his.quality.domain.QualityScore;
|
||||
import com.healthlink.his.quality.mapper.*;
|
||||
import com.healthlink.his.web.quality.appservice.IEmrQualityAppService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -10,7 +10,7 @@ import java.util.*;
|
||||
import java.util.Date;
|
||||
@Service
|
||||
public class EmrQualityAppServiceImpl implements IEmrQualityAppService {
|
||||
@Autowired private EmrQualityScoreMapper scoreMapper;
|
||||
@Autowired private QualityScoreMapper scoreMapper;
|
||||
@Autowired private EmrDefectMapper defectMapper;
|
||||
|
||||
@Override
|
||||
@@ -43,9 +43,9 @@ public class EmrQualityAppServiceImpl implements IEmrQualityAppService {
|
||||
@Override
|
||||
public List<Map<String, Object>> getQualityScores(Long encounterId) {
|
||||
List<Map<String, Object>> scores = new ArrayList<>();
|
||||
List<EmrQualityScore> list = scoreMapper.selectList(new LambdaQueryWrapper<EmrQualityScore>()
|
||||
.eq(EmrQualityScore::getEncounterId, encounterId).orderByDesc(EmrQualityScore::getCreateTime));
|
||||
for (EmrQualityScore s : list) {
|
||||
List<QualityScore> list = scoreMapper.selectList(new LambdaQueryWrapper<QualityScore>()
|
||||
.eq(QualityScore::getEncounterId, encounterId).orderByDesc(QualityScore::getCreateTime));
|
||||
for (QualityScore s : list) {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("id", s.getId()); m.put("emrType", s.getEmrType()); m.put("score", s.getScore());
|
||||
m.put("grade", s.getGrade()); m.put("checkType", s.getCheckType()); m.put("checkerName", s.getCheckerName());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.healthlink.his.web.quality.appservice.impl;
|
||||
|
||||
import com.healthlink.his.quality.domain.EmrDefect;
|
||||
import com.healthlink.his.quality.domain.EmrQualityScore;
|
||||
import com.healthlink.his.quality.domain.QualityScore;
|
||||
import com.healthlink.his.quality.mapper.EmrDefectMapper;
|
||||
import com.healthlink.his.quality.mapper.EmrQualityScoreMapper;
|
||||
import com.healthlink.his.quality.mapper.QualityScoreMapper;
|
||||
import com.healthlink.his.web.quality.appservice.ITerminalQualityAppService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -16,7 +16,7 @@ import java.util.*;
|
||||
public class TerminalQualityAppServiceImpl implements ITerminalQualityAppService {
|
||||
|
||||
@Autowired
|
||||
private EmrQualityScoreMapper scoreMapper;
|
||||
private QualityScoreMapper scoreMapper;
|
||||
@Autowired
|
||||
private EmrDefectMapper defectMapper;
|
||||
|
||||
@@ -44,7 +44,7 @@ public class TerminalQualityAppServiceImpl implements ITerminalQualityAppService
|
||||
String grade = calculateGrade(score);
|
||||
|
||||
// 保存评分记录
|
||||
EmrQualityScore qualityScore = new EmrQualityScore();
|
||||
QualityScore qualityScore = new QualityScore();
|
||||
qualityScore.setEncounterId(encounterId);
|
||||
qualityScore.setScore(score);
|
||||
qualityScore.setMaxScore(new BigDecimal(100));
|
||||
@@ -69,15 +69,15 @@ public class TerminalQualityAppServiceImpl implements ITerminalQualityAppService
|
||||
result.put("encounterId", encounterId);
|
||||
|
||||
// 获取评分记录
|
||||
List<EmrQualityScore> scores = scoreMapper.selectList(
|
||||
new LambdaQueryWrapper<EmrQualityScore>()
|
||||
.eq(EmrQualityScore::getEncounterId, encounterId)
|
||||
.eq(EmrQualityScore::getCheckType, "TERMINAL")
|
||||
.orderByDesc(EmrQualityScore::getCreateTime)
|
||||
List<QualityScore> scores = scoreMapper.selectList(
|
||||
new LambdaQueryWrapper<QualityScore>()
|
||||
.eq(QualityScore::getEncounterId, encounterId)
|
||||
.eq(QualityScore::getCheckType, "TERMINAL")
|
||||
.orderByDesc(QualityScore::getCreateTime)
|
||||
);
|
||||
|
||||
if (!scores.isEmpty()) {
|
||||
EmrQualityScore latestScore = scores.get(0);
|
||||
QualityScore latestScore = scores.get(0);
|
||||
result.put("score", latestScore.getScore());
|
||||
result.put("grade", latestScore.getGrade());
|
||||
result.put("checkTime", latestScore.getCreateTime());
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.*;
|
||||
@RequestMapping("/business-analytics")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class BusinessAnalyticsController {
|
||||
public class QualityBusinessAnalyticsController {
|
||||
|
||||
private final IBusinessAnalyticsService analyticsService;
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RestController("reportBusinessAnalyticsController")
|
||||
@RequestMapping("/report/analytics")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RestController("reportDrgAnalysisController")
|
||||
@RequestMapping("/report/drg")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
CREATE TABLE cdss_rule (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
rule_code VARCHAR(32) NOT NULL,
|
||||
rule_name VARCHAR(100) NOT NULL,
|
||||
rule_type VARCHAR(20) NOT NULL,
|
||||
condition_expr TEXT NOT NULL,
|
||||
suggestion TEXT NOT NULL,
|
||||
severity VARCHAR(16) DEFAULT 'INFO',
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
update_by VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE TABLE cdss_alert (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
encounter_id BIGINT NOT NULL,
|
||||
patient_id BIGINT NOT NULL,
|
||||
rule_id BIGINT NOT NULL,
|
||||
alert_type VARCHAR(20) NOT NULL,
|
||||
alert_message TEXT NOT NULL,
|
||||
severity VARCHAR(16) NOT NULL,
|
||||
acknowledged BOOLEAN DEFAULT FALSE,
|
||||
acknowledged_by BIGINT,
|
||||
acknowledged_time TIMESTAMP,
|
||||
tenant_id BIGINT DEFAULT 0,
|
||||
delete_flag CHAR(1) DEFAULT '0',
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(64)
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark, delete_flag)
|
||||
VALUES
|
||||
('CDSS', 10001, 10, 'cdss', 'cdss/cdssAlerts/index', NULL, 1, 0, 'C', '0', '0', 'infection:cdss:list', 'fa:', 'admin', CURRENT_TIMESTAMP, NULL, NULL, '临床决策支持', 0),
|
||||
('区域共享', 20081, 10, 'regionalshare', 'esbmanage/regionalshare/index', NULL, 1, 0, 'C', '0', '0', 'infection:regional:list', 'fa:', 'admin', CURRENT_TIMESTAMP, NULL, NULL, '区域医疗信息共享', 0),
|
||||
('EMR数据仓库', 20201, 10, 'data-warehouse', 'emr/data-warehouse/index', NULL, 1, 0, 'C', '0', '0', 'infection:emr:list', 'fa:', 'admin', CURRENT_TIMESTAMP, NULL, NULL, '病历数据仓库', 0),
|
||||
('病案统计明细', 20051, 10, 'statistics-detail', 'mrhomepage/statistics-detail/index', NULL, 1, 0, 'C', '0', '0', 'infection:mrhomepage:list', 'fa:', 'admin', CURRENT_TIMESTAMP, NULL, NULL, '病案统计明细', 0),
|
||||
('报表维度', 360, 10, 'ReportDimension', 'reportmanage/ReportDimension', NULL, 1, 0, 'C', '0', '0', 'infection:report:list', 'fa:', 'admin', CURRENT_TIMESTAMP, NULL, NULL, '报表维度', 0);
|
||||
@@ -0,0 +1,11 @@
|
||||
UPDATE sys_menu SET perms = 'mrhomepage:mrhomepage:list' WHERE menu_name = '病案统计明细' AND perms = 'infection:mrhomepage:list';
|
||||
UPDATE sys_menu SET perms = 'reportmanage:report:list' WHERE menu_name = '报表维度' AND perms = 'infection:report:list';
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
|
||||
(1, (SELECT menu_id FROM sys_menu WHERE menu_name = 'CDSS告警' LIMIT 1)),
|
||||
(1, (SELECT menu_id FROM sys_menu WHERE menu_name = 'CDSS规则' LIMIT 1)),
|
||||
(1, (SELECT menu_id FROM sys_menu WHERE menu_name = '区域共享' LIMIT 1)),
|
||||
(1, (SELECT menu_id FROM sys_menu WHERE menu_name = 'EMR数据仓库' LIMIT 1)),
|
||||
(1, (SELECT menu_id FROM sys_menu WHERE menu_name = '病案统计明细' LIMIT 1)),
|
||||
(1, (SELECT menu_id FROM sys_menu WHERE menu_name = '报表维度' LIMIT 1))
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -601,6 +601,7 @@
|
||||
WHERE T1.delete_flag = '0'
|
||||
AND T1.refund_medicine_id IS NULL
|
||||
AND T1.status_enum != #{stopped}
|
||||
AND T1.status_enum != #{dispenseCompleted}
|
||||
AND T1.generate_source_enum = #{doctorPrescription}
|
||||
AND CASE
|
||||
WHEN T1.status_enum = #{draft}
|
||||
@@ -737,6 +738,7 @@
|
||||
AND af.delete_flag = '0'
|
||||
WHERE T1.delete_flag = '0'
|
||||
AND T1.status_enum != #{stopped}
|
||||
AND T1.status_enum != #{dispenseCompleted}
|
||||
AND T1.generate_source_enum = #{doctorPrescription}
|
||||
AND T2.category_code != #{transfer}
|
||||
AND T2.category_code != #{discharge}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.healthlink.his.infection.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("cdss_alert")
|
||||
public class CdssAlert extends HisBaseEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("encounter_id")
|
||||
private Long encounterId;
|
||||
@TableField("patient_id")
|
||||
private Long patientId;
|
||||
@TableField("rule_id")
|
||||
private Long ruleId;
|
||||
@TableField("alert_type")
|
||||
private String alertType;
|
||||
@TableField("alert_message")
|
||||
private String alertMessage;
|
||||
@TableField("severity")
|
||||
private String severity;
|
||||
@TableField("acknowledged")
|
||||
private Boolean acknowledged;
|
||||
@TableField("acknowledged_by")
|
||||
private Long acknowledgedBy;
|
||||
@TableField("acknowledged_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date acknowledgedTime;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.healthlink.his.infection.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.core.common.core.domain.HisBaseEntity;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("cdss_rule")
|
||||
public class CdssRule extends HisBaseEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("rule_code")
|
||||
private String ruleCode;
|
||||
@TableField("rule_name")
|
||||
private String ruleName;
|
||||
@TableField("rule_type")
|
||||
private String ruleType;
|
||||
@TableField("condition_expr")
|
||||
private String conditionExpr;
|
||||
@TableField("suggestion")
|
||||
private String suggestion;
|
||||
@TableField("severity")
|
||||
private String severity;
|
||||
@TableField("enabled")
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.healthlink.his.infection.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CdssAlertMapper extends BaseMapper<CdssAlert> {
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.healthlink.his.infection.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CdssRuleMapper extends BaseMapper<CdssRule> {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.healthlink.his.infection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
|
||||
public interface ICdssAlertService extends IService<CdssAlert> {
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.healthlink.his.infection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
|
||||
public interface ICdssRuleService extends IService<CdssRule> {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.healthlink.his.infection.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.infection.domain.CdssAlert;
|
||||
import com.healthlink.his.infection.mapper.CdssAlertMapper;
|
||||
import com.healthlink.his.infection.service.ICdssAlertService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CdssAlertServiceImpl extends ServiceImpl<CdssAlertMapper, CdssAlert> implements ICdssAlertService {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.healthlink.his.infection.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.healthlink.his.infection.domain.CdssRule;
|
||||
import com.healthlink.his.infection.mapper.CdssRuleMapper;
|
||||
import com.healthlink.his.infection.service.ICdssRuleService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> implements ICdssRuleService {
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public interface OrderExecuteRecordMapper extends BaseMapper<OrderExecuteRecord>
|
||||
"GROUP BY m.doctor_name ORDER BY totalOrders DESC")
|
||||
List<Map<String, Object>> selectGroupByDoctor();
|
||||
|
||||
@Select("SELECT COUNT(*) FROM order_execute_record e " +
|
||||
"WHERE e.delete_flag = '0' " +
|
||||
"AND e.execute_status IN ('pending', 'in_progress', 'overdue', 'executing')")
|
||||
long countUnclosedWarnings();
|
||||
|
||||
@Select("SELECT e.order_no AS orderNo, e.patient_name AS patientName, e.order_type AS orderType, " +
|
||||
"COALESCE(m.department_name, '未知') AS department, " +
|
||||
"COALESCE(m.doctor_name, '未知') AS doctorName, " +
|
||||
@@ -49,6 +54,7 @@ public interface OrderExecuteRecordMapper extends BaseMapper<OrderExecuteRecord>
|
||||
"LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " +
|
||||
"WHERE e.delete_flag = '0' " +
|
||||
"AND e.execute_status IN ('pending', 'in_progress', 'overdue', 'executing') " +
|
||||
"ORDER BY e.create_time DESC")
|
||||
List<Map<String, Object>> selectUnclosedWarnings();
|
||||
"ORDER BY e.create_time DESC " +
|
||||
"LIMIT #{limit} OFFSET #{offset}")
|
||||
List<Map<String, Object>> selectUnclosedWarningsPaged(@Param("limit") int limit, @Param("offset") int offset);
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import com.core.common.core.domain.HisBaseEntity;
|
||||
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;
|
||||
import java.math.BigDecimal;
|
||||
@Data @TableName("emr_quality_score") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false)
|
||||
public class EmrQualityScore extends HisBaseEntity {
|
||||
public class QualityScore extends HisBaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private Long encounterId; private Long patientId; private String emrType;
|
||||
private BigDecimal score; private BigDecimal maxScore; private String grade;
|
||||
private Long checkerId; private String checkerName; private String checkType; private String remark; private String delFlag;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
package com.healthlink.his.quality.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.healthlink.his.quality.domain.EmrQualityScore;
|
||||
import com.healthlink.his.quality.domain.QualityScore;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface EmrQualityScoreMapper extends BaseMapper<EmrQualityScore> {}
|
||||
@Mapper public interface QualityScoreMapper extends BaseMapper<QualityScore> {}
|
||||
@@ -2,7 +2,7 @@ import request from '@/utils/request'
|
||||
|
||||
export function evaluateRules(params) {
|
||||
return request({
|
||||
url: '/cdss/evaluate',
|
||||
url: '/infection/cdss/evaluate',
|
||||
method: 'post',
|
||||
params: params
|
||||
})
|
||||
@@ -10,7 +10,7 @@ export function evaluateRules(params) {
|
||||
|
||||
export function getAlerts(encounterId, query) {
|
||||
return request({
|
||||
url: '/cdss/alerts/' + encounterId,
|
||||
url: '/infection/cdss/alerts/' + encounterId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
@@ -18,7 +18,7 @@ export function getAlerts(encounterId, query) {
|
||||
|
||||
export function acknowledgeAlert(id, data) {
|
||||
return request({
|
||||
url: '/cdss/alerts/' + id + '/acknowledge',
|
||||
url: '/infection/cdss/alerts/' + id + '/acknowledge',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import request from '@/utils/request'
|
||||
|
||||
export function getCdssRuleList(query) {
|
||||
return request({
|
||||
url: '/cdss/rules',
|
||||
url: '/infection/cdss/rules',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
@@ -10,7 +10,7 @@ export function getCdssRuleList(query) {
|
||||
|
||||
export function addCdssRule(data) {
|
||||
return request({
|
||||
url: '/cdss/rules',
|
||||
url: '/infection/cdss/rules',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
@@ -18,7 +18,7 @@ export function addCdssRule(data) {
|
||||
|
||||
export function updateCdssRule(data) {
|
||||
return request({
|
||||
url: '/cdss/rules',
|
||||
url: '/infection/cdss/rules',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
@@ -26,7 +26,7 @@ export function updateCdssRule(data) {
|
||||
|
||||
export function deleteCdssRule(id) {
|
||||
return request({
|
||||
url: '/cdss/rules/' + id,
|
||||
url: '/infection/cdss/rules/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
105
healthlink-his-ui/src/views/cdss/cdssRules/index.vue
Normal file
105
healthlink-his-ui/src/views/cdss/cdssRules/index.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="100px">
|
||||
<el-form-item label="规则类型" prop="ruleType">
|
||||
<el-select v-model="queryParams.ruleType" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="药物审查" value="DRUG_REVIEW" />
|
||||
<el-option label="诊断提示" value="DIAGNOSIS" />
|
||||
<el-option label="检验预警" value="LAB_ALERT" />
|
||||
<el-option label="用药禁忌" value="CONTRAINDICATION" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="严重程度" prop="severity">
|
||||
<el-select v-model="queryParams.severity" placeholder="请选择" clearable style="width: 140px">
|
||||
<el-option label="CRITICAL" value="CRITICAL" />
|
||||
<el-option label="HIGH" value="HIGH" />
|
||||
<el-option label="MEDIUM" value="MEDIUM" />
|
||||
<el-option label="LOW" value="LOW" />
|
||||
<el-option label="INFO" value="INFO" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="规则名称" prop="keyword">
|
||||
<el-input v-model="queryParams.keyword" placeholder="搜索规则名称" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<vxe-table :data="ruleList" :loading="loading" border stripe height="auto">
|
||||
<vxe-column type="seq" title="序号" width="70" />
|
||||
<vxe-column field="ruleCode" title="规则编码" width="120" />
|
||||
<vxe-column field="ruleName" title="规则名称" min-width="180" show-overflow />
|
||||
<vxe-column field="ruleType" title="规则类型" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.ruleType }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="severity" title="严重程度" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="severityTagType(row.severity)" effect="dark">{{ row.severity }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="conditionExpr" title="条件表达式" min-width="200" show-overflow />
|
||||
<vxe-column field="actionExpr" title="执行动作" min-width="200" show-overflow />
|
||||
<vxe-column field="status" title="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '启用' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="sortOrder" title="排序" width="70" align="center" />
|
||||
</vxe-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CdssRules">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { getCdssRuleList } from '@/api/cdss/cdssRule'
|
||||
|
||||
const ruleList = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
|
||||
const queryParams = reactive({
|
||||
ruleType: '',
|
||||
severity: '',
|
||||
keyword: ''
|
||||
})
|
||||
|
||||
const severityTagType = (severity) => {
|
||||
const map = { CRITICAL: 'danger', HIGH: 'warning', MEDIUM: '', LOW: 'info', INFO: 'info' }
|
||||
return map[severity] || 'info'
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {}
|
||||
if (queryParams.ruleType) params.ruleType = queryParams.ruleType
|
||||
if (queryParams.severity) params.severity = queryParams.severity
|
||||
if (queryParams.keyword) params.keyword = queryParams.keyword
|
||||
const res = await getCdssRuleList(params)
|
||||
if (res.code === 200) {
|
||||
ruleList.value = res.data || []
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
const resetQuery = () => {
|
||||
queryParams.ruleType = ''
|
||||
queryParams.severity = ''
|
||||
queryParams.keyword = ''
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -81,7 +81,6 @@
|
||||
class="inspection-table"
|
||||
:row-config="{ keyField: 'applicationId', keyField: 'itemId' }"
|
||||
@checkbox-change="handleSelectionChange"
|
||||
@current-change="handleRowClick"
|
||||
@cell-click="handleCellClick"
|
||||
>
|
||||
<vxe-column
|
||||
@@ -2142,15 +2141,32 @@ const handleSave = () => {
|
||||
let hasErrors = false
|
||||
|
||||
// 修复【#406】:保存前尝试从 props 同步患者信息,避免因加载时序导致信息缺失
|
||||
if ((!formData.patientName?.trim() || !formData.medicalrecordNumber?.trim()) && props.patientInfo && props.patientInfo.encounterId) {
|
||||
formData.patientName = props.patientInfo.patientName || ''
|
||||
formData.medicalrecordNumber = props.patientInfo.identifierNo || ''
|
||||
formData.encounterId = props.patientInfo.encounterId || ''
|
||||
formData.visitNo = props.patientInfo.busNo || ''
|
||||
formData.patientId = props.patientInfo.patientId || ''
|
||||
formData.applyDepartment = props.patientInfo.organizationName || ''
|
||||
formData.applyDeptCode = props.patientInfo.organizationName || ''
|
||||
formData.applyOrganizationId = props.patientInfo.orgId || ''
|
||||
if (props.patientInfo && props.patientInfo.encounterId) {
|
||||
// encounterId 来自 adm_encounter 表,lab_apply 表无此字段,需始终从 props 同步
|
||||
if (!formData.encounterId) {
|
||||
formData.encounterId = props.patientInfo.encounterId || ''
|
||||
}
|
||||
if (!formData.patientName?.trim()) {
|
||||
formData.patientName = props.patientInfo.patientName || ''
|
||||
}
|
||||
if (!formData.medicalrecordNumber?.trim()) {
|
||||
formData.medicalrecordNumber = props.patientInfo.identifierNo || ''
|
||||
}
|
||||
if (!formData.visitNo) {
|
||||
formData.visitNo = props.patientInfo.busNo || ''
|
||||
}
|
||||
if (!formData.patientId) {
|
||||
formData.patientId = props.patientInfo.patientId || ''
|
||||
}
|
||||
if (!formData.applyDepartment) {
|
||||
formData.applyDepartment = props.patientInfo.organizationName || ''
|
||||
}
|
||||
if (!formData.applyDeptCode) {
|
||||
formData.applyDeptCode = props.patientInfo.organizationName || ''
|
||||
}
|
||||
if (!formData.applyOrganizationId) {
|
||||
formData.applyOrganizationId = props.patientInfo.orgId || ''
|
||||
}
|
||||
}
|
||||
|
||||
// P0:检查患者信息是否已加载
|
||||
@@ -2457,10 +2473,13 @@ const handleDelete = (row) => {
|
||||
}
|
||||
|
||||
// 单元格点击 - 点击表格行时加载申请单详情
|
||||
const handleCellClick = (row, column) => {
|
||||
const handleCellClick = (params) => {
|
||||
// vxe-table cell-click 事件参数是 { row, column, $event, ... } 对象,需安全提取行数据
|
||||
const row = params.row || params;
|
||||
const column = params.column || params;
|
||||
// 如果点击的是操作列或展开列,不触发数据填充
|
||||
if (column.property === '操作' || column.label === '操作' ||
|
||||
column.type === 'expand' || column.type === 'selection') {
|
||||
if (column.type === 'expand' || column.type === 'selection' ||
|
||||
column.title === '操作' || column.property === '操作') {
|
||||
return;
|
||||
}
|
||||
// 点击表格行时,将该申请单的数据加载到表单中
|
||||
@@ -2470,15 +2489,6 @@ const handleCellClick = (row, column) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 行点击事件处理
|
||||
const handleRowClick = (currentRow, oldRow) => {
|
||||
// 点击表格行时,将该申请单的数据加载到表单中
|
||||
// 使用 applyNo 判断是否有效,同时检查是否处于删除状态
|
||||
if (currentRow && currentRow.applyNo && !isDeleting.value) {
|
||||
loadApplicationToForm(currentRow);
|
||||
}
|
||||
}
|
||||
|
||||
// 提取公共方法加载申请单到表单
|
||||
const loadApplicationToForm = async (row) => {
|
||||
// 停止申请日期实时更新(加载已保存的申请单)
|
||||
|
||||
@@ -332,7 +332,9 @@ function resetQuery() {
|
||||
getSummaryList();
|
||||
}
|
||||
|
||||
function getDetails(row) {
|
||||
function getDetails(params) {
|
||||
// cell-click 事件参数是 { row, column, $event, ... } 对象,需安全提取行数据
|
||||
const row = params.row || params;
|
||||
loading.value = true;
|
||||
getFromSummaryDetails({ summaryNo: row.busNo }).then((res) => {
|
||||
summaryDetailsData.value = res.data;
|
||||
|
||||
Reference in New Issue
Block a user