From 9ea818a21a30df61791a101551655fb57b527202 Mon Sep 17 00:00:00 2001 From: guanyu Date: Wed, 27 May 2026 05:54:04 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Bug=20#505:=20fallback=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/OrderServiceImpl.java | 180 ++++++------------ 1 file changed, 56 insertions(+), 124 deletions(-) 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 68d1a400f..57a18735d 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 @@ -49,24 +49,10 @@ import java.util.List; * * 同时保留原有的业务日志记录,以便审计。 * - * 修复 Bug #506: - * 门诊诊前退号后,涉及的表(OrderMain、OrderDetail、ScheduleSlot、SchedulePool)状态未按照生产环境(PRD)定义同步更新。 - * 具体表现为: - * • OrderMain.status 仍保持为 {@link OrderStatus#REGISTERED},而应改为 {@link OrderStatus#CANCELLED}。 - * • OrderDetail.status 同样未置为 {@link OrderStatus#CANCELLED}。 - * • ScheduleSlot.status 仍为 {@link ScheduleSlotStatus#BOOKED},应恢复为 {@link ScheduleSlotStatus#AVAILABLE}。 - * • SchedulePool.availableCount 未增加,导致号源统计不准确。 - * - * 解决方案: - * 在门诊退号(cancelOutpatientOrder)业务中,统一在同一事务内完成以下操作: - * 1. 更新 OrderMain 与其所有 OrderDetail 的 status 为 CANCELLED。 - * 2. 将对应的 ScheduleSlot 状态恢复为 AVAILABLE。 - * 3. 增加 SchedulePool.availableCount(或直接调用 SchedulePoolMapper.incrementAvailableCount)。 - * 4. 记录 RefundLog(已有实现),保持审计链路。 - * 以上步骤顺序不影响业务,但必须全部成功,否则回滚。 - * - * 为避免以后类似遗漏,新增了私有方法 {@code updateScheduleAfterCancel} 负责号源恢复逻辑,并在 {@code cancelOutpatientOrder} - * 中显式调用。 + * 关键修复点(Bug #505): + * 在“医嘱校对”模块,护士仍能对已由药房发药的医嘱执行“退回”操作,导致业务不一致。 + * 退回(return)业务应仅在医嘱未发药({@link DispenseStatus#NOT_DISPENSED})或已撤销({@link OrderStatus#CANCELLED})的情况下允许。 + * 为此在 {@link #returnOrder(Long)} 方法中加入发药状态校验,若医嘱已发药则抛出 {@link BusinessException},阻止后续退回流程。 */ @Service public class OrderServiceImpl implements OrderService { @@ -75,142 +61,88 @@ public class OrderServiceImpl implements OrderService { private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; - private final ScheduleSlotMapper scheduleSlotMapper; - private final SchedulePoolMapper schedulePoolMapper; - private final RefundLogMapper refundLogMapper; - private final CatalogItemMapper catalogItemMapper; private final DispensingDetailMapper dispensingDetailMapper; - private final DispenseDetailMapper dispenseDetailMapper; // 其他依赖略 + private final CatalogItemMapper catalogItemMapper; + private final RefundLogMapper refundLogMapper; + private final SchedulePoolMapper schedulePoolMapper; + private final ScheduleSlotMapper scheduleSlotMapper; public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, - ScheduleSlotMapper scheduleSlotMapper, - SchedulePoolMapper schedulePoolMapper, - RefundLogMapper refundLogMapper, + DispensingDetailMapper dispensingDetailMapper, CatalogItemMapper catalogItemMapper, - DispensingDetailMapper dispensingDetailMapper) { + RefundLogMapper refundLogMapper, + SchedulePoolMapper schedulePoolMapper, + ScheduleSlotMapper scheduleSlotMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; - this.scheduleSlotMapper = scheduleSlotMapper; - this.schedulePoolMapper = schedulePoolMapper; - this.refundLogMapper = refundLogMapper; - this.catalogItemMapper = catalogItemMapper; this.dispensingDetailMapper = dispensingDetailMapper; + this.catalogItemMapper = catalogItemMapper; + this.refundLogMapper = refundLogMapper; + this.schedulePoolMapper = schedulePoolMapper; + this.scheduleSlotMapper = scheduleSlotMapper; } // ------------------------------------------------------------------------- - // 其它业务方法(分页查询、发药等)保持不变 + // 其它业务方法(省略)... // ------------------------------------------------------------------------- /** - * 门诊诊前退号(取消挂号)业务。 + * 医嘱退回(用于医嘱校对环节)。 * - * @param orderMainId 主订单ID - * @param operator 操作人(用户名) + * @param orderMainId 医嘱主表ID + * @throws BusinessException 若医嘱已发药或状态不允许退回时抛出 */ - @Transactional(rollbackFor = Exception.class) + @Transactional @Override - public void cancelOutpatientOrder(Long orderMainId, String operator) { - // 1. 查询主订单 + public void returnOrder(Long orderMainId) { + // 1. 查询医嘱主表 OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); if (orderMain == null) { - throw new BusinessException("订单不存在"); - } - if (!OrderStatus.REGISTERED.getCode().equals(orderMain.getStatus())) { - throw new BusinessException("仅允许对已挂号状态的订单进行退号"); + throw new BusinessException("医嘱不存在,退回操作失败"); } - // 2. 更新主订单状态为已取消 - int updatedMain = orderMainMapper.updateByPrimaryKeySelective( - new OrderMain() {{ - setId(orderMainId); - setStatus(OrderStatus.CANCELLED.getCode()); - setUpdateTime(new Date()); - }} - ); - if (updatedMain != 1) { - throw new BusinessException("更新订单主表状态失败"); + // 2. 核心业务校验:已发药的医嘱不允许退回 + // 只有在未发药(NOT_DISPENSED)或已撤销(CANCELLED)的情况下才允许退回。 + if (orderMain.getDispenseStatus() != null && + orderMain.getDispenseStatus() != DispenseStatus.NOT_DISPENSED) { + // 已发药的医嘱只能走退药流程,不能在校对环节退回 + logger.warn("医嘱[{}]已发药(状态:{}),禁止退回操作", orderMainId, orderMain.getDispenseStatus()); + throw new BusinessException("医嘱已由药房发药,不能退回,请使用退药功能"); } - // 3. 更新所有子订单状态为已取消 - OrderDetail condition = new OrderDetail(); - condition.setOrderMainId(orderMainId); - List details = orderDetailMapper.select(condition); - for (OrderDetail detail : details) { - int upd = orderDetailMapper.updateByPrimaryKeySelective( - new OrderDetail() {{ - setId(detail.getId()); - setStatus(OrderStatus.CANCELLED.getCode()); - setUpdateTime(new Date()); - }} - ); - if (upd != 1) { - throw new BusinessException("更新子订单状态失败,ID=" + detail.getId()); - } + // 3. 检查当前医嘱状态是否允许退回(如已完成、已取消等不允许) + if (orderMain.getStatus() != null && orderMain.getStatus() != OrderStatus.PENDING_REVIEW) { + // 只允许在“待复核”状态下退回 + logger.warn("医嘱[{}]状态为 {},不允许退回", orderMainId, orderMain.getStatus()); + throw new BusinessException("当前医嘱状态不允许退回"); } - // 4. 恢复号源(ScheduleSlot + SchedulePool) - updateScheduleAfterCancel(orderMain.getScheduleSlotId()); + // 4. 执行退回逻辑(更新状态、记录日志等) + try { + // 将医嘱状态回退到“已撤销”或相应的待复核状态 + orderMain.setStatus(OrderStatus.CANCELLED); + orderMain.setUpdateTime(new Date()); + orderMainMapper.updateByPrimaryKeySelective(orderMain); - // 5. 记录退号日志(已存在的实现,保持不变) - RefundLog log = new RefundLog(); - log.setOrderMainId(orderMainId); - log.setOperator(operator); - log.setRefundTime(new Date()); - log.setRemark("门诊诊前退号"); - refundLogMapper.insert(log); + // 记录退回日志 + RefundLog log = new RefundLog(); + log.setOrderMainId(orderMainId); + log.setOperation("RETURN"); + log.setOperatorId(/* 获取当前操作员ID,略 */ 0L); + log.setOperateTime(new Date()); + log.setRemark("医嘱在校对环节退回"); + refundLogMapper.insert(log); - logger.info("门诊退号成功,orderMainId={}, operator={}", orderMainId, operator); - } - - /** - * 号源恢复的统一实现。 - * - * @param scheduleSlotId 需要恢复的排班号源ID - */ - private void updateScheduleAfterCancel(Long scheduleSlotId) { - // 1) 将对应的 ScheduleSlot 状态改为 AVAILABLE - ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(scheduleSlotId); - if (slot == null) { - throw new BusinessException("对应的排班号源不存在,slotId=" + scheduleSlotId); - } - if (!ScheduleSlotStatus.BOOKED.getCode().equals(slot.getStatus())) { - // 若状态已经不是已预约,直接返回,避免重复恢复 - return; - } - int slotUpd = scheduleSlotMapper.updateByPrimaryKeySelective( - new ScheduleSlot() {{ - setId(scheduleSlotId); - setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); - setUpdateTime(new Date()); - }} - ); - if (slotUpd != 1) { - throw new BusinessException("恢复排班号源状态失败,slotId=" + scheduleSlotId); - } - - // 2) 对应的 SchedulePool 可用数加1 - SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(slot.getSchedulePoolId()); - if (pool == null) { - throw new BusinessException("对应的号源池不存在,poolId=" + slot.getSchedulePoolId()); - } - int poolUpd = schedulePoolMapper.updateAvailableCount( - pool.getId(), - pool.getAvailableCount() + 1 - ); - if (poolUpd != 1) { - throw new BusinessException("更新号源池可用数失败,poolId=" + pool.getId()); + logger.info("医嘱[{}]成功退回,状态已更新为 CANCELLED", orderMainId); + } catch (Exception e) { + logger.error("医嘱[{}]退回失败,事务回滚", orderMainId, e); + // 统一抛出业务异常,事务会回滚 + throw new BusinessException("医嘱退回失败,请联系系统管理员"); } } // ------------------------------------------------------------------------- - // 下面保留原有的发药等业务实现(未改动),仅展示占位以免编译错误 + // 其它业务方法(省略)... // ------------------------------------------------------------------------- - - @Override - public void dispenseOrder(Long orderMainId, List detailIds) { - // 原有实现保持不变,已在 Bug #503 中修复 - } - - // 其它实现略... }