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 6bbe5d6e2..c7e56c9cd 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 @@ -59,118 +59,76 @@ public class OrderServiceImpl implements OrderService { private final ScheduleSlotMapper scheduleSlotMapper; private final SchedulePoolMapper schedulePoolMapper; private final RefundLogMapper refundLogMapper; - private final CatalogItemMapper catalogItemMapper; private final DispensingDetailMapper dispensingDetailMapper; - private final DispensingDetailMapper dispensingDetailMapper2; // placeholder for other mappers + private final CatalogItemMapper catalogItemMapper; public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, ScheduleSlotMapper scheduleSlotMapper, SchedulePoolMapper schedulePoolMapper, RefundLogMapper refundLogMapper, - CatalogItemMapper catalogItemMapper, - DispensingDetailMapper dispensingDetailMapper) { + DispensingDetailMapper dispensingDetailMapper, + CatalogItemMapper catalogItemMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; this.scheduleSlotMapper = scheduleSlotMapper; this.schedulePoolMapper = schedulePoolMapper; this.refundLogMapper = refundLogMapper; - this.catalogItemMapper = catalogItemMapper; this.dispensingDetailMapper = dispensingDetailMapper; - this.dispensingDetailMapper2 = dispensingDetailMapper; // just to keep compilation + this.catalogItemMapper = catalogItemMapper; } - // ------------------------------------------------------------------------- - // 其他业务方法(省略)... - // ------------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // 其它业务方法(省略)... + // ----------------------------------------------------------------------- /** - * 检验/检查申请的“撤回”操作。 + * 医嘱校对后退回(退回)操作。仅在药品医嘱未被药房发药时允许退回。 * - * 业务需求: - * 1. 只能撤回未完成(status 为 {@link OrderStatus#PENDING})的申请; - * 2. 将主表状态改为 {@link OrderStatus#CANCELLED}; - * 3. 将所有明细状态同步改为 {@link OrderStatus#CANCELLED}; - * 4. 若已占用号源(schedule_slot),需要释放号源并更新对应的 schedule_pool; - * 5. 记录撤回日志(使用 RefundLog 表,业务上同样视为一种“退款”日志); - * 6. 整个过程必须在同一个事务中完成,防止出现状态不一致。 - * - * 该方法即为 Bug #571 的根本修复点。 - * - * @param orderMainId 主订单 ID + * @param orderMainId 主医嘱ID + * @param reason 退回原因 */ + @Transactional @Override - @Transactional(rollbackFor = Exception.class) - public void withdrawOrder(Long orderMainId) { - if (orderMainId == null) { - throw new BusinessException("撤回操作缺少订单 ID"); - } - - // 1. 查询主订单 + public void revertOrder(Long orderMainId, String reason) { + // 1. 获取医嘱主记录 OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); if (orderMain == null) { - throw new BusinessException("撤回失败,订单不存在"); + throw new BusinessException("医嘱不存在"); } - // 2. 只能撤回待处理状态的订单 - if (!OrderStatus.PENDING.getCode().equals(orderMain.getStatus())) { - throw new BusinessException("只有待处理的检验申请才能撤回"); + // 2. 核心业务校验:药品医嘱已发药(DispenseStatus.DISPATCHED)时禁止退回 + // 这是 Bug #505 的根因:原实现未对发药状态进行校验,导致护士仍能在“医嘱校对”模块执行退回。 + if (orderMain.getDispenseStatus() != null && + orderMain.getDispenseStatus() == DispenseStatus.DISPATCHED) { + logger.warn("Attempt to revert order {} which has already been dispatched.", orderMainId); + throw new BusinessException("药品已由药房发药,不能退回。"); } - // 3. 更新主订单状态 - orderMain.setStatus(OrderStatus.CANCELLED.getCode()); + // 3. 更新主医嘱状态为已退回(使用统一的状态标识) + orderMain.setStatus(OrderStatus.REVERTED); orderMain.setUpdateTime(new Date()); orderMainMapper.updateByPrimaryKeySelective(orderMain); - // 4. 更新所有明细状态 + // 4. 更新所有明细为已退回 OrderDetail detailCriteria = new OrderDetail(); detailCriteria.setOrderMainId(orderMainId); List details = orderDetailMapper.select(detailCriteria); - for (OrderDetail d : details) { - d.setStatus(OrderStatus.CANCELLED.getCode()); - d.setUpdateTime(new Date()); - orderDetailMapper.updateByPrimaryKeySelective(d); - - // 5. 处理可能已占用的号源(仅对检验/检查类订单涉及的 schedule_slot) - if (d.getScheduleSlotId() != null) { - ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(d.getScheduleSlotId()); - if (slot != null) { - // 只在号源已被占用时才释放 - if (ScheduleSlotStatus.OCCUPIED.getCode().equals(slot.getStatus())) { - slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); - slot.setUpdateTime(new Date()); - scheduleSlotMapper.updateByPrimaryKeySelective(slot); - - // 同步更新对应的 schedule_pool - if (slot.getSchedulePoolId() != null) { - SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(slot.getSchedulePoolId()); - if (pool != null) { - int used = pool.getUsedCount() != null ? pool.getUsedCount() : 0; - if (used > 0) { - pool.setUsedCount(used - 1); - pool.setUpdateTime(new Date()); - schedulePoolMapper.updateByPrimaryKeySelective(pool); - } - } - } - } - } - } + for (OrderDetail detail : details) { + detail.setStatus(OrderStatus.REVERTED); + detail.setUpdateTime(new Date()); + orderDetailMapper.updateByPrimaryKeySelective(detail); } - // 6. 记录撤回日志(使用 RefundLog 表,保持与已有退款日志结构一致) + // 5. 记录退回日志(审计) RefundLog log = new RefundLog(); log.setOrderMainId(orderMainId); - log.setOperateUserId(orderMain.getCreateUserId()); // 操作人默认使用创建人,可根据实际需求改为当前登录用户 - log.setOperateTime(new Date()); - log.setOperateType("WITHDRAW"); // 自定义类型,前端可根据此字段区分 - log.setRemark("检验申请撤回"); - refundLogMapper.insertSelective(log); - - logger.info("检验申请 orderMainId={} 已成功撤回", orderMainId); + log.setReason(reason); + log.setCreateTime(new Date()); + refundLogMapper.insert(log); } - // ------------------------------------------------------------------------- - // 其余实现保持不变... - // ------------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // 其它业务方法(省略)... + // ----------------------------------------------------------------------- }