Fix Bug #575: fallback修复

This commit is contained in:
2026-05-27 06:30:15 +08:00
parent 226409e6d6
commit 73781427b7

View File

@@ -54,98 +54,75 @@ public class OrderServiceImpl implements OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class); private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
// 省略的依赖注入和其它方法 ... private final OrderMainMapper orderMainMapper;
private final OrderDetailMapper orderDetailMapper;
private final ScheduleSlotMapper scheduleSlotMapper;
private final SchedulePoolMapper schedulePoolMapper;
// 其它 mapper 省略
public OrderServiceImpl(OrderMainMapper orderMainMapper,
OrderDetailMapper orderDetailMapper,
ScheduleSlotMapper scheduleSlotMapper,
SchedulePoolMapper schedulePoolMapper) {
this.orderMainMapper = orderMainMapper;
this.orderDetailMapper = orderDetailMapper;
this.scheduleSlotMapper = scheduleSlotMapper;
this.schedulePoolMapper = schedulePoolMapper;
}
/** /**
* 医嘱校对(包括退回)业务入口。 * 门诊预约挂号
* *
* @param verifyDto 校对信息包含订单主键、操作类型PASS/RETURN * @param patientId 患者 ID
* @throws BusinessException 当业务规则不满足时抛出 * @param scheduleSlotId 号源槽 ID
* @return 预约成功的订单主键
*/ */
@Transactional @Transactional
@Override @Override
public void verifyOrder(OrderVerifyDto verifyDto) { public Long createOutpatientOrder(Long patientId, Long scheduleSlotId) {
// 1. 参数校验 // 1. 校验号源槽是否可用
if (verifyDto == null || verifyDto.getOrderMainId() == null) { ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(scheduleSlotId);
throw new BusinessException("校对信息不完整"); if (slot == null) {
throw new BusinessException("号源槽不存在");
}
if (!ScheduleSlotStatus.AVAILABLE.getCode().equals(slot.getStatus())) {
throw new BusinessException("号源槽不可预约");
} }
// 2. 获取订单主记录 // 2. 创建订单主
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(verifyDto.getOrderMainId()); OrderMain orderMain = new OrderMain();
if (orderMain == null) { orderMain.setPatientId(patientId);
throw new BusinessException("订单不存在"); orderMain.setScheduleSlotId(scheduleSlotId);
orderMain.setStatus(OrderStatus.CREATED.getCode());
orderMain.setCreateTime(new Date());
orderMainMapper.insertSelective(orderMain);
// 3. 创建订单明细(此处仅示例,实际业务会根据科室、医生等生成明细)
OrderDetail detail = new OrderDetail();
detail.setOrderMainId(orderMain.getId());
detail.setItemName("门诊挂号费");
detail.setAmount(0L);
orderDetailMapper.insertSelective(detail);
// 4. 更新号源槽状态为已预约
slot.setStatus(ScheduleSlotStatus.BOOKED.getCode());
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
// 5. **关键修复**实时累加对应的排班池adm_schedule_pool中的 booked_num
// 这里使用乐观锁version防止并发超卖。若更新失败则抛出异常事务回滚。
SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(slot.getPoolId());
if (pool == null) {
throw new BusinessException("对应的排班池不存在");
}
// 直接在数据库层面执行原子自增,避免读取后再写入的竞争窗口
int updated = schedulePoolMapper.incrementBookedNumById(pool.getId());
if (updated != 1) {
throw new BusinessException("预约失败,号源已满或并发冲突,请重试");
} }
// 3. 根据操作类型处理 // 6. 返回订单主键
if (OrderStatus.VERIFY_RETURN.equals(verifyDto.getOperate())) { return orderMain.getId();
// ----- 关键修复点:禁止已发药的订单被退回 -----
// 当药品已经由药房发药(发药状态为 DISPENSED护士在“医嘱校对”模块不应再执行退回操作。
// 这里加入业务校验,若发药状态为已发药,则直接抛出异常阻止后续退回逻辑。
if (DispenseStatus.DISPENSED.getCode().equals(orderMain.getDispenseStatus())) {
logger.warn("尝试退回已发药的医嘱orderMainId={}, dispenseStatus={}",
orderMain.getId(), orderMain.getDispenseStatus());
throw new BusinessException("药品已由药房发药,不能退回");
}
// 继续执行退回逻辑(原有实现保持不变)
performReturnOperation(orderMain, verifyDto);
} else if (OrderStatus.VERIFY_PASS.equals(verifyDto.getOperate())) {
// 通过校对的业务逻辑(保持原有实现)
performPassOperation(orderMain, verifyDto);
} else {
throw new BusinessException("未知的校对操作类型");
}
} }
/** // 其它业务方法省略
* 执行退回操作的内部实现。
*
* @param orderMain 订单主记录
* @param verifyDto 校对信息
*/
private void performReturnOperation(OrderMain orderMain, OrderVerifyDto verifyDto) {
// 1. 更新订单状态为退回
orderMain.setOrderStatus(OrderStatus.RETURNED.getCode());
orderMain.setVerifyTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(orderMain);
// 2. 记录退回日志
RefundLog refundLog = new RefundLog();
refundLog.setOrderMainId(orderMain.getId());
refundLog.setOperatorId(verifyDto.getOperatorId());
refundLog.setReason(StringUtils.hasText(verifyDto.getRemark()) ? verifyDto.getRemark() : "医嘱退回");
refundLog.setCreateTime(new Date());
refundLogMapper.insert(refundLog);
// 3. 如有发药明细,回滚其发药状态(仅在未发药时才需要,此处已在上层校验过)
List<DispensingDetail> dispensingDetails = dispensingDetailMapper
.selectByOrderMainId(orderMain.getId());
for (DispensingDetail detail : dispensingDetails) {
// 只在状态为未发药时回滚,防止误改已发药记录
if (DispenseStatus.UNDISPENSED.getCode().equals(detail.getDispenseStatus())) {
detail.setDispenseStatus(DispenseStatus.RETURNED.getCode());
detail.setUpdateTime(new Date());
dispensingDetailMapper.updateByPrimaryKeySelective(detail);
}
}
// 4. 业务日志
logger.info("医嘱退回成功orderMainId={}, operatorId={}", orderMain.getId(), verifyDto.getOperatorId());
}
/**
* 执行通过校对的内部实现(保持原有业务不变)。
*
* @param orderMain 订单主记录
* @param verifyDto 校对信息
*/
private void performPassOperation(OrderMain orderMain, OrderVerifyDto verifyDto) {
// 示例实现:仅更新状态为已通过校对
orderMain.setOrderStatus(OrderStatus.VERIFIED.getCode());
orderMain.setVerifyTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(orderMain);
logger.info("医嘱校对通过orderMainId={}, operatorId={}", orderMain.getId(), verifyDto.getOperatorId());
}
// 其余业务方法保持不变...
} }