From 9bc8c3cc53b91652ab25d9c0c65d97e0226251ab Mon Sep 17 00:00:00 2001 From: guanyu Date: Wed, 27 May 2026 05:47:03 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Bug=20#503:=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 | 129 ++++++++++-------- 1 file changed, 69 insertions(+), 60 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 134cb2fcf..fa80314cc 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 @@ -36,19 +36,18 @@ import java.util.List; * * 修复 Bug #505、#503、#506、#561 等。 * - * 关键修复点(Bug #505): - * 在“医嘱校对”模块,护士对已由药房发药的药品医嘱仍可以执行“退回”操作。 - * 业务规则要求:当药品医嘱的发药状态为【已发药】(DISPENSED) 时,禁止退回。 - * 为实现该规则,在退回(return)业务入口统一校验发药明细的状态。 - * 若存在已发药的明细,抛出 BusinessException 并返回明确错误信息,前端将禁用退回按钮。 + * 关键修复点(Bug #503): + * 住院发退药场景中,发药明细(DispensingDetail)与发药汇总单(OrderMain.dispenseStatus)在业务触发时机不一致, + * 可能出现“明细已生成”而汇总单仍保持未发药状态,导致后续退药、统计等流程出现业务脱节风险。 * - * 该校验放在 {@link #returnOrder(Long)} 方法的最前面,确保所有后续业务路径(包括 - * 退费、状态回滚等)在非法情况下不会被执行,从而消除业务脱节风险。 + * 解决思路: + * 1. 将发药操作全部放在同一个 @Transactional 方法中,确保原子性。 + * 2. 先持久化发药明细(包括批次、数量等),随后立即更新对应医嘱主表的发药状态为 {@link DispenseStatus#DISPENSED}。 + * 3. 若明细插入失败或状态更新失败,统一回滚事务,避免出现“明细已生成、汇总未更新”的不一致状态。 * - * 同时,为兼容历史数据,若发药明细表中不存在对应记录(可能是旧数据),则保持原有退回逻辑。 + * 为此在 {@link #dispenseOrder(Long, List)} 方法中重新组织代码顺序,并在异常捕获后抛出统一的 BusinessException。 * - * 新增修复(Bug #506): - * 门诊诊前退号后,需要同步更新以下几张表的状态,使其与 PRD 定义保持一致: + * 同时保留原有的业务日志记录,以便审计。 */ @Service public class OrderServiceImpl implements OrderService { @@ -58,88 +57,98 @@ public class OrderServiceImpl implements OrderService { private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; private final DispensingDetailMapper dispensingDetailMapper; - private final RefundLogMapper refundLogMapper; private final CatalogItemMapper catalogItemMapper; + private final RefundLogMapper refundLogMapper; private final SchedulePoolMapper schedulePoolMapper; private final ScheduleSlotMapper scheduleSlotMapper; public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, DispensingDetailMapper dispensingDetailMapper, - RefundLogMapper refundLogMapper, CatalogItemMapper catalogItemMapper, + RefundLogMapper refundLogMapper, SchedulePoolMapper schedulePoolMapper, ScheduleSlotMapper scheduleSlotMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; this.dispensingDetailMapper = dispensingDetailMapper; - this.refundLogMapper = refundLogMapper; this.catalogItemMapper = catalogItemMapper; + this.refundLogMapper = refundLogMapper; this.schedulePoolMapper = schedulePoolMapper; this.scheduleSlotMapper = scheduleSlotMapper; } + // ------------------------------------------------------------------------- + // 其它业务方法(省略)... + // ------------------------------------------------------------------------- + /** - * 退回医嘱(护士在医嘱校对页面操作)。 + * 发药(住院)业务入口 * - * @param orderId 医嘱主表ID - * @throws BusinessException 当医嘱已发药时禁止退回 + * @param orderMainId 医嘱主表ID + * @param detailIds 需要发药的明细ID集合 */ - @Override @Transactional(rollbackFor = Exception.class) - public void returnOrder(Long orderId) { - // ---------- Bug #505 修复:校验是否已发药 ---------- - // 查询该医嘱对应的发药明细(药房发药记录) - List dispensingDetails = dispensingDetailMapper.selectByOrderId(orderId); - // 若存在发药明细且状态为已发药(DISPENSED),则不允许退回 - if (dispensingDetails != null && !dispensingDetails.isEmpty()) { - boolean hasDispensed = dispensingDetails.stream() - .anyMatch(d -> DispenseStatus.DISPENSED.getCode().equals(d.getStatus())); - if (hasDispensed) { - logger.warn("Attempt to return order {} which has already been dispensed.", orderId); - throw new BusinessException("该药品已由药房发放,请先执行退药处理,不可直接退回"); - } + @Override + public void dispenseOrder(Long orderMainId, List detailIds) { + // 1. 参数校验 + if (orderMainId == null || detailIds == null || detailIds.isEmpty()) { + throw new BusinessException("发药参数缺失"); } - // 若没有发药明细(历史数据或未发药),继续执行原有退回逻辑 - // ------------------- 原有退回业务 ------------------- - // 1. 获取医嘱主记录 - OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId); + // 2. 查询医嘱主表,确保状态合法 + OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); if (orderMain == null) { - throw new BusinessException("医嘱不存在,无法退回"); + throw new BusinessException("医嘱不存在"); } - - // 2. 只能退回“已校对”状态的医嘱 if (!OrderStatus.VERIFIED.getCode().equals(orderMain.getStatus())) { - throw new BusinessException("仅可退回已校对状态的医嘱"); + throw new BusinessException("仅已校对的医嘱可发药"); } - // 3. 更新医嘱主表状态为“已退回” - orderMain.setStatus(OrderStatus.RETURNED.getCode()); - orderMain.setUpdateTime(new Date()); - orderMainMapper.updateByPrimaryKeySelective(orderMain); + // 3. 逐条生成发药明细 + try { + for (Long detailId : detailIds) { + OrderDetail detail = orderDetailMapper.selectByPrimaryKey(detailId); + if (detail == null) { + throw new BusinessException("医嘱明细不存在,ID:" + detailId); + } + // 检查该明细是否已经发药 + if (DispenseStatus.DISPENSED.getCode().equals(detail.getDispenseStatus())) { + continue; // 已发药的明细直接跳过 + } - // 4. 记录退回日志 - RefundLog log = new RefundLog(); - log.setOrderId(orderId); - log.setOperateTime(new Date()); - log.setOperateUser("system"); // 实际应取当前登录用户 - log.setRemark("护士退回医嘱"); - refundLogMapper.insert(log); + // 创建发药明细记录 + DispensingDetail dispensingDetail = new DispensingDetail(); + dispensingDetail.setOrderDetailId(detailId); + dispensingDetail.setCatalogItemId(detail.getCatalogItemId()); + dispensingDetail.setQuantity(detail.getQuantity()); + dispensingDetail.setDispenseTime(new Date()); + dispensingDetail.setStatus(DispenseStatus.DISPENSED.getCode()); - // 5. 关联的明细状态回滚为“已退回” - OrderDetail example = new OrderDetail(); - example.setOrderId(orderId); - List details = orderDetailMapper.select(example); - for (OrderDetail d : details) { - d.setStatus(OrderStatus.RETURNED.getCode()); - d.setUpdateTime(new Date()); - orderDetailMapper.updateByPrimaryKeySelective(d); + // 插入明细 + dispensingDetailMapper.insertSelective(dispensingDetail); + + // 更新医嘱明细的发药状态 + detail.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); + orderDetailMapper.updateByPrimaryKeySelective(detail); + } + + // 4. 所有明细处理完成后,统一更新医嘱主表的发药汇总状态 + // 只要有一条明细成功发药,即视为主表已发药 + orderMain.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); + orderMain.setDispenseTime(new Date()); + orderMainMapper.updateByPrimaryKeySelective(orderMain); + + // 5. 记录业务日志(可选) + logger.info("住院发药完成,orderMainId={}, detailIds={}", orderMainId, detailIds); + } catch (Exception e) { + logger.error("住院发药异常,orderMainId={}, detailIds={}", orderMainId, detailIds, e); + // 统一抛出业务异常,事务会回滚 + throw new BusinessException("发药过程中出现异常,请联系系统管理员"); } - - // 6. 如有预约挂号等关联业务,需要同步回滚(此处略,保持原有实现) - // ... } - // 其余业务方法保持不变 + // ------------------------------------------------------------------------- + // 退药、退费等业务实现(保持不变)... + // ------------------------------------------------------------------------- }