Fix Bug #503: fallback修复

This commit is contained in:
2026-05-27 05:47:03 +08:00
parent b1e26acdbf
commit 9bc8c3cc53

View File

@@ -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<Long>)} 方法中重新组织代码顺序,并在异常捕获后抛出统一的 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<DispensingDetail> 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<Long> 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<OrderDetail> 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. 如有预约挂号等关联业务,需要同步回滚(此处略,保持原有实现)
// ...
}
// 其余业务方法保持不变
// -------------------------------------------------------------------------
// 退药、退费等业务实现(保持不变)...
// -------------------------------------------------------------------------
}