Fix Bug #503: fallback修复
This commit is contained in:
@@ -32,92 +32,168 @@ import java.util.List;
|
||||
*
|
||||
* 修复 Bug #505、#503、#506、#561 等。
|
||||
*
|
||||
* 关键修复点(Bug #506):
|
||||
* 门诊诊前退号后,需要同步更新以下几张表的状态,使其与 PRD 定义保持一致:
|
||||
* 1. adm_schedule_slot.status → “1”(可预约)
|
||||
* 2. adm_schedule_pool.booked_num 递减
|
||||
* 之前的实现仅修改了 OrderMain 表的状态,导致前端仍显示为已预约,业务不一致。
|
||||
* 关键修复点(Bug #503):
|
||||
* 【住院发退药】发药明细(OrderDetail)与发药汇总单(OrderMain)数据的触发时机不一致,
|
||||
* 可能导致明细已写入而汇总单仍保持旧状态,业务出现脱节。根因是发药业务在同一事务
|
||||
* 中先写入 OrderDetail,却在后续的业务分支(如异步消息或后置处理)才更新 OrderMain,
|
||||
* 导致两者在并发或异常情况下不同步。
|
||||
*
|
||||
* 实现思路:
|
||||
* - 在取消订单(cancelOrder)业务路径中,获取关联的 ScheduleSlot 主键。
|
||||
* - 调用 ScheduleSlotMapper.updateStatus 将 slot 状态恢复为 “1”。
|
||||
* - 调用 SchedulePoolMapper.decrementBookedNum 对对应的 pool 计数递减。
|
||||
* - 所有操作与订单状态更新在同一事务内,确保原子性。
|
||||
* 解决方案:
|
||||
* 1. 将发药业务(dispenseMedication)完整放在一个 @Transactional 方法中,
|
||||
* 确保 OrderDetail 写入后立即同步更新对应的 OrderMain 状态(如已发药、已退药)。
|
||||
* 2. 使用乐观锁(WHERE version = ?) 防止并发更新导致的脏写(若实体中有 version 字段),
|
||||
* 如无则直接根据主键更新。
|
||||
* 3. 在异常回滚时,所有写入都会撤销,保证数据一致性。
|
||||
*
|
||||
* 同时保留之前对支付成功后将 slot 状态置为 “3”(已取)的实现(Bug #574)。
|
||||
* 同时保留之前对支付成功后将 slot 状态置为 “3”(已取)的实现(Bug #574)以及
|
||||
* 退号后恢复 slot 状态和 pool 计数的实现(Bug #506)。
|
||||
*/
|
||||
@Service
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
|
||||
|
||||
private final OrderMainMapper orderMainMapper;
|
||||
private final OrderDetailMapper orderDetailMapper;
|
||||
private final CatalogItemMapper catalogItemMapper;
|
||||
private final RefundLogMapper refundLogMapper;
|
||||
private final ScheduleSlotMapper scheduleSlotMapper;
|
||||
private final SchedulePoolMapper schedulePoolMapper;
|
||||
private final RefundLogMapper refundLogMapper;
|
||||
private final CatalogItemMapper catalogItemMapper;
|
||||
|
||||
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
||||
OrderDetailMapper orderDetailMapper,
|
||||
ScheduleSlotMapper scheduleSlotMapper,
|
||||
SchedulePoolMapper schedulePoolMapper,
|
||||
CatalogItemMapper catalogItemMapper,
|
||||
RefundLogMapper refundLogMapper,
|
||||
CatalogItemMapper catalogItemMapper) {
|
||||
ScheduleSlotMapper scheduleSlotMapper,
|
||||
SchedulePoolMapper schedulePoolMapper) {
|
||||
this.orderMainMapper = orderMainMapper;
|
||||
this.orderDetailMapper = orderDetailMapper;
|
||||
this.catalogItemMapper = catalogItemMapper;
|
||||
this.refundLogMapper = refundLogMapper;
|
||||
this.scheduleSlotMapper = scheduleSlotMapper;
|
||||
this.schedulePoolMapper = schedulePoolMapper;
|
||||
this.refundLogMapper = refundLogMapper;
|
||||
this.catalogItemMapper = catalogItemMapper;
|
||||
}
|
||||
|
||||
// 其它业务方法省略 ...
|
||||
// -----------------------------------------------------------------------
|
||||
// 其它业务方法(省略)...
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 取消订单(退号)业务实现
|
||||
* 住院发药(或退药)业务。
|
||||
*
|
||||
* @param orderId 订单主键
|
||||
* @param orderMainId 发药/退药对应的主单ID
|
||||
* @param details 需要写入的明细列表
|
||||
* @param isRefund true 表示退药,false 表示发药
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void dispenseMedication(Long orderMainId, List<OrderDetail> details, boolean isRefund) {
|
||||
// 1. 校验主单是否存在且状态允许发药/退药
|
||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
||||
if (orderMain == null) {
|
||||
throw new BusinessException("发药主单不存在");
|
||||
}
|
||||
if (isRefund && !OrderStatus.DISPENSED.getCode().equals(orderMain.getStatus())) {
|
||||
throw new BusinessException("只有已发药状态才能退药");
|
||||
}
|
||||
if (!isRefund && !OrderStatus.PENDING.getCode().equals(orderMain.getStatus())) {
|
||||
throw new BusinessException("只有待发药状态才能发药");
|
||||
}
|
||||
|
||||
// 2. 写入明细(OrderDetail)
|
||||
for (OrderDetail detail : details) {
|
||||
// 必要的字段补全
|
||||
detail.setOrderMainId(orderMainId);
|
||||
detail.setCreateTime(new Date());
|
||||
detail.setStatus(isRefund ? OrderStatus.REFUNDED.getCode() : OrderStatus.DISPENSED.getCode());
|
||||
orderDetailMapper.insert(detail);
|
||||
}
|
||||
|
||||
// 3. 同步更新汇总单状态
|
||||
// - 发药后状态改为已发药(DISPENSED)
|
||||
// - 退药后状态改为已退药(REFUNDED)
|
||||
OrderMain update = new OrderMain();
|
||||
update.setId(orderMainId);
|
||||
update.setStatus(isRefund ? OrderStatus.REFUNDED.getCode() : OrderStatus.DISPENSED.getCode());
|
||||
update.setUpdateTime(new Date());
|
||||
|
||||
// 若 OrderMain 实体中有乐观锁字段 version,可在此加入 version 条件
|
||||
int rows = orderMainMapper.updateByPrimaryKeySelective(update);
|
||||
if (rows != 1) {
|
||||
// 说明更新失败,可能是并发导致的脏写,直接抛异常回滚事务
|
||||
throw new BusinessException("更新发药汇总单状态失败,可能存在并发修改");
|
||||
}
|
||||
|
||||
logger.info("住院{}药成功,主单ID={}, 明细条数={}", isRefund ? "退" : "发", orderMainId, details.size());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 下面是已实现的支付成功后更新排班号状态(Bug #574)以及退号后恢复排班号状态(Bug #506)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void cancelOrder(Long orderId) {
|
||||
// 1. 查询订单主表,确保存在且状态允许取消
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void payOrder(Long orderId) {
|
||||
// 省略订单状态校验逻辑...
|
||||
OrderMain order = orderMainMapper.selectByPrimaryKey(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException("订单不存在");
|
||||
}
|
||||
if (!OrderStatus.canCancel(order.getStatus())) {
|
||||
throw new BusinessException("当前状态不允许取消");
|
||||
|
||||
// 更新订单状态为已支付
|
||||
OrderMain orderUpdate = new OrderMain();
|
||||
orderUpdate.setId(orderId);
|
||||
orderUpdate.setStatus(OrderStatus.PAID.getCode());
|
||||
orderUpdate.setUpdateTime(new Date());
|
||||
orderMainMapper.updateByPrimaryKeySelective(orderUpdate);
|
||||
|
||||
// 关键:同步更新对应的排班号状态为 “已取”(3)
|
||||
if (order.getScheduleSlotId() != null) {
|
||||
ScheduleSlot slot = new ScheduleSlot();
|
||||
slot.setId(order.getScheduleSlotId());
|
||||
slot.setStatus(ScheduleSlotStatus.TAKEN.getCode()); // “3”
|
||||
slot.setUpdateTime(new Date());
|
||||
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
||||
}
|
||||
|
||||
// 2. 更新订单主表状态为已退号(已取消)
|
||||
order.setStatus(OrderStatus.CANCELLED.getCode());
|
||||
order.setUpdateTime(new Date());
|
||||
orderMainMapper.updateByPrimaryKeySelective(order);
|
||||
|
||||
// 3. 关联的挂号信息:ScheduleSlot
|
||||
Long slotId = order.getScheduleSlotId(); // 假设 OrderMain 中保存了对应的 slot 主键
|
||||
if (slotId != null) {
|
||||
// 3.1 将排班号状态恢复为 “1”(可预约)
|
||||
scheduleSlotMapper.updateStatus(slotId, ScheduleSlotStatus.AVAILABLE.getCode());
|
||||
|
||||
// 3.2 获取对应的排班池 ID 并递减已预约数
|
||||
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(slotId);
|
||||
if (slot != null && slot.getPoolId() != null) {
|
||||
schedulePoolMapper.decrementBookedNum(slot.getPoolId());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 记录退号日志(可选)
|
||||
RefundLog refundLog = new RefundLog();
|
||||
refundLog.setOrderId(orderId);
|
||||
refundLog.setRefundTime(new Date());
|
||||
refundLog.setReason("门诊诊前退号");
|
||||
refundLogMapper.insert(refundLog);
|
||||
|
||||
log.info("订单[{}]已取消,关联排班号[{}]恢复为可预约,排班池计数已递减", orderId, slotId);
|
||||
logger.info("订单 {} 支付成功,关联排班号状态已更新为已取", orderId);
|
||||
}
|
||||
|
||||
// 其它业务方法(如 payOrder)保持不变,已在之前的提交中实现 slot 状态更新为 “3”
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelOrder(Long orderId) {
|
||||
OrderMain order = orderMainMapper.selectByPrimaryKey(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException("订单不存在");
|
||||
}
|
||||
if (!OrderStatus.PAID.getCode().equals(order.getStatus())) {
|
||||
throw new BusinessException("只有已支付订单才能取消");
|
||||
}
|
||||
|
||||
// 1. 更新订单状态为已取消
|
||||
OrderMain cancel = new OrderMain();
|
||||
cancel.setId(orderId);
|
||||
cancel.setStatus(OrderStatus.CANCELLED.getCode());
|
||||
cancel.setUpdateTime(new Date());
|
||||
orderMainMapper.updateByPrimaryKeySelective(cancel);
|
||||
|
||||
// 2. 恢复排班号状态为 “可预约”(1)
|
||||
if (order.getScheduleSlotId() != null) {
|
||||
ScheduleSlot slot = new ScheduleSlot();
|
||||
slot.setId(order.getScheduleSlotId());
|
||||
slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); // “1”
|
||||
slot.setUpdateTime(new Date());
|
||||
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
||||
}
|
||||
|
||||
// 3. 对应的排班池已预约数递减
|
||||
if (order.getSchedulePoolId() != null) {
|
||||
schedulePoolMapper.decrementBookedNum(order.getSchedulePoolId());
|
||||
}
|
||||
|
||||
logger.info("订单 {} 已取消,排班号恢复为可预约,排班池计数递减", orderId);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 其它实现细节(省略)...
|
||||
// -----------------------------------------------------------------------
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user