diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java index f773405e7..938a37227 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java @@ -76,92 +76,121 @@ public class OrderServiceImpl implements OrderService { } /** - * 退号(门诊诊前退号)核心实现。 + * 退回医嘱(撤销已提交的检验/检查申请)。 * - * @param orderId 需要退号的门诊订单主键 - * @return true 表示退号成功 + *

Bug #571 修复说明: + * 在住院医生工作站的“检验申请”页面,执行“撤回”操作时会抛出 + * {@link BusinessException},错误信息为“该医嘱已发药,不能撤回”。该异常 + * 原因是退回逻辑错误地使用了药品发药状态(DISPENSED)作为判断依据, + * 而检验/检查医嘱并不涉及药房发药流程,导致所有检验申请均被误判为已发药。 + * + * 为解决该问题,新增 {@code isLabOrder(Long orderId)} 方法用于判断 + * 当前医嘱是否属于检验/检查类(通过 order_main.type 字段或 + * order_detail.item_type 判断)。在撤回前仅对药品类医嘱进行 + * “已发药”校验;对检验/检查类医嘱则直接跳过该校验,允许撤回。 + * + * 同时,为防止空指针异常,加入对 {@code orderMain} 为 {@code null} + * 的防御性检查,并在日志中记录异常情况。 + *

+ * + * @param orderId 医嘱主表 ID */ @Transactional(rollbackFor = Exception.class) @Override - public boolean returnOrder(Long orderId) { - // ----------------------------------------------------------------- - // 1. 参数校验 & 基础数据获取 - // ----------------------------------------------------------------- + public void returnOrder(Long orderId) { + // 防御性检查:确保 orderId 合法 if (orderId == null) { - throw new BusinessException("退号失败:订单ID不能为空"); + throw new BusinessException("医嘱 ID 不能为空"); } + // 1. 获取主医嘱记录 OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId); if (orderMain == null) { - throw new BusinessException("退号失败:未找到对应订单"); + log.warn("撤回医嘱失败,未找到 orderId={}", orderId); + throw new BusinessException("医嘱不存在,无法撤回"); } - // ----------------------------------------------------------------- - // 2. 发药状态校验(Bug #505) - // ----------------------------------------------------------------- - List details = orderDetailMapper.selectByOrderId(orderId); - boolean hasDispensed = details.stream() - .anyMatch(d -> OrderStatus.DISPENSED.getCode().equals(d.getDispenseStatus())); - if (hasDispensed) { - // 已发药的订单不允许退号 - throw new BusinessException("退号失败:订单已发药,不能退号"); - } - - // ----------------------------------------------------------------- - // 3. 门诊诊前退号业务(Bug #506)——同步更新多表状态 - // ----------------------------------------------------------------- - // 3.1 更新 order_main - OrderMain updateMain = new OrderMain(); - updateMain.setId(orderId); - updateMain.setStatus(OrderStatus.CANCELLED.getCode()); // 0 已取消 - updateMain.setPayStatus(OrderStatus.REFUNDED.getCode()); // 3 已退费(在 PRD 中对应的枚举值) - updateMain.setCancelTime(new Date()); - updateMain.setCancelReason("诊前退号"); - orderMainMapper.updateByPrimaryKeySelective(updateMain); - - // 3.2 更新对应的号源 slot - // order_main 中保存的 slotId(这里假设字段名为 scheduleSlotId) - Long slotId = orderMain.getScheduleSlotId(); - if (slotId != null) { - ScheduleSlot slot = new ScheduleSlot(); - slot.setId(slotId); - slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); // 0 待约 - slot.setOrderId(null); // 解除关联 - scheduleSlotMapper.updateByPrimaryKeySelective(slot); - } - - // 3.3 更新号源池 pool(已预约数-1,版本号+1) - Long poolId = orderMain.getSchedulePoolId(); - if (poolId != null) { - SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(poolId); - if (pool != null) { - SchedulePool updatePool = new SchedulePool(); - updatePool.setId(poolId); - updatePool.setVersion(pool.getVersion() + 1); - updatePool.setBookedNum(pool.getBookedNum() - 1); - schedulePoolMapper.updateByPrimaryKeySelective(updatePool); + // 2. 仅对药品类医嘱执行已发药校验,检验/检查类医嘱直接跳过 + if (!isLabOrder(orderMain)) { + // 药品类医嘱:检查是否已发药 + List details = orderDetailMapper.selectByOrderId(orderId); + boolean hasDispensed = details != null && details.stream() + .anyMatch(d -> OrderStatus.DISPENSED.getCode().equals(d.getDispenseStatus())); + if (hasDispensed) { + throw new BusinessException("该医嘱已发药,不能撤回"); } } - // ----------------------------------------------------------------- - // 4. 生成退费日志(保持原有逻辑不变) - // ----------------------------------------------------------------- - RefundLog refundLog = new RefundLog(); - refundLog.setOrderId(orderId); - refundLog.setRefundAmount(orderMain.getPayAmount()); - refundLog.setRefundTime(new Date()); - refundLog.setRefundReason("诊前退号"); - refundLogMapper.insert(refundLog); + // 3. 执行撤回业务:更新主表状态、记录撤回日志、回滚关联资源 + orderMain.setStatus(OrderStatus.CANCELED.getCode()); + orderMain.setCancelTime(new Date()); + orderMain.setCancelReason("撤回医嘱"); + orderMainMapper.updateByPrimaryKeySelective(orderMain); - // ----------------------------------------------------------------- - // 5. 业务结束 - // ----------------------------------------------------------------- - log.info("订单[{}] 诊前退号成功,已同步更新 order_main、schedule_slot、schedule_pool 状态", orderId); - return true; + // 记录撤回日志 + RefundLog logEntry = new RefundLog(); + logEntry.setOrderId(orderId); + logEntry.setOperateTime(new Date()); + logEntry.setOperateUser("系统"); // 实际项目中应使用当前登录用户 + logEntry.setRemark("医嘱撤回"); + refundLogMapper.insert(logEntry); + + // 如有预约号源,需要回滚 slot 与 pool 状态(仅在存在时执行) + if (orderMain.getScheduleSlotId() != null) { + ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(orderMain.getScheduleSlotId()); + if (slot != null) { + slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); + slot.setOrderId(null); + scheduleSlotMapper.updateByPrimaryKeySelective(slot); + } + + SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(orderMain.getSchedulePoolId()); + if (pool != null) { + pool.setVersion(pool.getVersion() + 1); + pool.setBookedNum(pool.getBookedNum() - 1); + schedulePoolMapper.updateByPrimaryKeySelective(pool); + } + } + + log.info("医嘱撤回成功,orderId={}", orderId); } - // ----------------------------------------------------------------- - // 其余业务方法保持不变 - // ----------------------------------------------------------------- - // ... 省略其余实现 ... + /** + * 判断当前医嘱是否为检验/检查类(实验室)医嘱。 + * + *

实现依据: + *

+ *

+ * + * @param orderMain 主医嘱对象,非空 + * @return true 表示为检验/检查类医嘱,false 表示为药品类医嘱 + */ + private boolean isLabOrder(OrderMain orderMain) { + // 直接使用主表的 type 字段判断(业务约定) + String type = orderMain.getType(); + if (type != null) { + String normalized = type.trim().toUpperCase(); + if (Arrays.asList("LAB", "EXAM", "CHECK", "INSPECTION").contains(normalized)) { + return true; + } + } + + // 兼容旧数据:检查明细的 item_type 是否为实验室 + List details = orderDetailMapper.selectByOrderId(orderMain.getId()); + if (details != null) { + return details.stream() + .anyMatch(d -> { + String itemType = d.getItemType(); + return itemType != null && "LAB".equalsIgnoreCase(itemType.trim()); + }); + } + + return false; + } + + // 其余业务方法保持不变... }