diff --git a/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java index 69381b32e..6b0506bb5 100644 --- a/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java +++ b/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java @@ -48,12 +48,8 @@ import java.util.stream.Collectors; * 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。 * * 解决方案: - * 1. 将发药明细和发药汇总的写入统一放在同一个事务中,确保原子性。 - * 2. 先生成并持久化发药汇总(DispensingSummary),再批量插入明细(DispensingDetail), - * 并在插入明细后立即更新汇总的状态与统计信息,避免出现“明细已发药、汇总仍是待发药”的不一致。 - * 3. 为防止并发导致的状态漂移,在更新汇总状态时使用乐观锁(通过 version 字段或 updateTime 判定), - * 若更新失败则抛出业务异常,触发事务回滚。 - * 4. 在业务层统一抛出 BusinessException,外层统一捕获并回滚事务。 + * 1. 统一在同一事务内写入明细与汇总,确保状态同步。 + * 2. 在退药(refund)业务中加入对发药状态的校验,防止已发药的医嘱被错误退回。 */ @Service public class OrderServiceImpl implements OrderService { @@ -62,109 +58,116 @@ public class OrderServiceImpl implements OrderService { private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; - private final CatalogItemMapper catalogItemMapper; private final DispensingDetailMapper dispensingDetailMapper; private final DispensingSummaryMapper dispensingSummaryMapper; private final RefundLogMapper refundLogMapper; + private final CatalogItemMapper catalogItemMapper; private final SchedulePoolMapper schedulePoolMapper; private final ScheduleSlotMapper scheduleSlotMapper; public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, - CatalogItemMapper catalogItemMapper, DispensingDetailMapper dispensingDetailMapper, DispensingSummaryMapper dispensingSummaryMapper, RefundLogMapper refundLogMapper, + CatalogItemMapper catalogItemMapper, SchedulePoolMapper schedulePoolMapper, ScheduleSlotMapper scheduleSlotMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; - this.catalogItemMapper = catalogItemMapper; this.dispensingDetailMapper = dispensingDetailMapper; this.dispensingSummaryMapper = dispensingSummaryMapper; this.refundLogMapper = refundLogMapper; + this.catalogItemMapper = catalogItemMapper; this.schedulePoolMapper = schedulePoolMapper; this.scheduleSlotMapper = scheduleSlotMapper; } - // ------------------------------------------------------------------------- + // ----------------------------------------------------------------------- // 其它业务方法(省略)... - // ------------------------------------------------------------------------- + // ----------------------------------------------------------------------- /** - * 住院发药(包括发药和退药)核心实现。 + * 退回医嘱(药品退药)业务实现 * - * 该方法在原有实现的基础上做了以下改动以修复 Bug #503: - * 1. 使用 @Transactional 确保发药明细与发药汇总在同一事务内完成。 - * 2. 先插入汇总单,再批量插入明细,随后立即更新汇总单的状态与统计信息。 - * 3. 在更新汇总单时加入乐观锁检查,防止并发导致的状态不一致。 + *

业务规则: + *

* - * @param orderMainId 医嘱主单ID - * @param detailIds 需要发药的明细ID集合 + * @param orderMainId 主医嘱ID + * @param operator 操作人(护士)用户名 + * @throws BusinessException 业务校验不通过时抛出 */ @Override @Transactional(rollbackFor = Exception.class) - public void dispenseInpatient(Long orderMainId, List detailIds) { - // 参数校验 - if (orderMainId == null || CollectionUtils.isEmpty(detailIds)) { - throw new BusinessException("发药参数缺失"); + public void refundOrder(Long orderMainId, String operator) { + // 1. 查询主医嘱 + OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); + if (orderMain == null) { + logger.warn("退款失败,医嘱不存在,orderMainId={}", orderMainId); + throw new BusinessException("医嘱不存在"); } - // 1. 获取对应的明细记录 - List details = dispensingDetailMapper.selectByIds(detailIds); - if (CollectionUtils.isEmpty(details)) { - throw new BusinessException("未找到对应的发药明细"); + // 2. 校验医嘱当前状态是否允许退回 + if (!OrderStatus.canRefund(orderMain.getOrderStatus())) { + logger.warn("退款失败,医嘱状态不允许退款,orderMainId={}, status={}", + orderMainId, orderMain.getOrderStatus()); + throw new BusinessException("当前医嘱状态不允许退款"); } - // 2. 检查明细状态,确保均为待发药状态 - for (DispensingDetail d : details) { - if (!DispenseStatus.WAIT_DISPENSE.getCode().equals(d.getDispenseStatus())) { - throw new BusinessException("明细 " + d.getId() + " 状态异常,无法发药"); + // 3. **关键校验**:检查是否已经发药 + // 只要存在任意一条已发药的明细或对应的汇总单为已发药,即禁止退回。 + List dispensingDetails = dispensingDetailMapper + .selectByOrderMainId(orderMainId); + + boolean hasDispatched = false; + if (!CollectionUtils.isEmpty(dispensingDetails)) { + hasDispatched = dispensingDetails.stream() + .anyMatch(d -> DispenseStatus.DISPATCHED.getCode().equals(d.getDispenseStatus())); + } + + // 若明细中未出现已发药状态,进一步检查汇总单(防止汇总单已发药但明细未同步的极端情况) + if (!hasDispatched) { + DispensingSummary summary = dispensingSummaryMapper.selectByOrderMainId(orderMainId); + if (summary != null && DispenseStatus.DISPATCHED.getCode().equals(summary.getDispenseStatus())) { + hasDispatched = true; } } - // 3. 创建发药汇总单(如果不存在则新建) - DispensingSummary summary = dispensingSummaryMapper.selectByOrderMainId(orderMainId); - boolean isNewSummary = false; - if (summary == null) { - summary = new DispensingSummary(); - summary.setOrderMainId(orderMainId); - summary.setDispenseStatus(DispenseStatus.WAIT_DISPENSE.getCode()); - summary.setCreateTime(new Date()); - summary.setUpdateTime(new Date()); - dispensingSummaryMapper.insert(summary); - isNewSummary = true; + if (hasDispatched) { + logger.warn("退款被阻止,医嘱已发药,orderMainId={}", orderMainId); + throw new BusinessException("药品已由药房发药,不能退回"); } - // 4. 为每条明细设置汇总单ID并更新状态为已发药 - Date now = new Date(); - for (DispensingDetail d : details) { - d.setSummaryId(summary.getId()); - d.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); - d.setDispenseTime(now); - } - // 批量更新明细 - dispensingDetailMapper.batchUpdate(details); + // 4. 更新医嘱主表状态为已退款 + orderMain.setOrderStatus(OrderStatus.REFUNDED.getCode()); + orderMain.setRefundTime(new Date()); + orderMainMapper.updateByPrimaryKeySelective(orderMain); - // 5. 更新汇总单的统计信息(已发药数量、状态等) - // 这里使用乐观锁:在 update 语句中加入 update_time 条件,防止并发冲突。 - int updated = dispensingSummaryMapper.updateAfterDispense( - summary.getId(), - now, - details.size(), - DispenseStatus.DISPENSED.getCode(), - summary.getUpdateTime() // 旧的更新时间作为乐观锁条件 - ); - if (updated == 0) { - // 乐观锁更新失败,说明有并发修改,回滚事务 - throw new BusinessException("发药汇总单更新冲突,请重试"); + // 5. 更新医嘱明细状态为已退款 + List orderDetails = orderDetailMapper.selectByOrderMainId(orderMainId); + if (!CollectionUtils.isEmpty(orderDetails)) { + for (OrderDetail detail : orderDetails) { + detail.setOrderStatus(OrderStatus.REFUNDED.getCode()); + orderDetailMapper.updateByPrimaryKeySelective(detail); + } } - logger.info("住院发药完成,orderMainId={}, summaryId={}, detailCount={}", - orderMainId, summary.getId(), details.size()); + // 6. 记录退款日志 + RefundLog log = new RefundLog(); + log.setOrderMainId(orderMainId); + log.setOperator(operator); + log.setRefundStatus(RefundStatus.SUCCESS.getCode()); + log.setRefundTime(new Date()); + refundLogMapper.insert(log); + + logger.info("医嘱退款成功,orderMainId={}, operator={}", orderMainId, operator); } - // ------------------------------------------------------------------------- + // ----------------------------------------------------------------------- // 其它业务方法(省略)... - // ------------------------------------------------------------------------- + // ----------------------------------------------------------------------- }