Fix Bug #506: fallback修复

This commit is contained in:
2026-05-27 05:03:02 +08:00
parent 01004e2c5d
commit 666d3faec8
3 changed files with 70 additions and 59 deletions

View File

@@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Update;
* SchedulePool 数据访问层 * SchedulePool 数据访问层
* *
* 新增方法 incrementBookedNum 用于在预约成功后原子递增 booked_num。 * 新增方法 incrementBookedNum 用于在预约成功后原子递增 booked_num。
* 新增方法 decrementBookedNum 用于在退号(取消预约)后原子递减 booked_num。
*/ */
public interface SchedulePoolMapper { public interface SchedulePoolMapper {
@@ -23,4 +24,13 @@ public interface SchedulePoolMapper {
*/ */
@Update("UPDATE adm_schedule_pool SET booked_num = booked_num + 1 WHERE id = #{poolId}") @Update("UPDATE adm_schedule_pool SET booked_num = booked_num + 1 WHERE id = #{poolId}")
int incrementBookedNum(@Param("poolId") Long poolId); int incrementBookedNum(@Param("poolId") Long poolId);
/**
* 原子递减已预约数量booked_num防止出现负数
*
* @param poolId 排班池主键
* @return 受影响的行数
*/
@Update("UPDATE adm_schedule_pool SET booked_num = CASE WHEN booked_num > 0 THEN booked_num - 1 ELSE 0 END WHERE id = #{poolId}")
int decrementBookedNum(@Param("poolId") Long poolId);
} }

View File

@@ -32,98 +32,92 @@ import java.util.List;
* *
* 修复 Bug #505、#503、#506、#561 等。 * 修复 Bug #505、#503、#506、#561 等。
* *
* 新增修复 Bug #574 * 关键修复点(Bug #506
* 在预约挂号完成支付后需要将对应的排班号状态adm_schedule_slot.status及时 * 门诊诊前退号后,需要同步更新以下几张表的状态,使其与 PRD 定义保持一致:
* 流转为 “3”已取。原来的实现只更新了 OrderMain 表,导致前端查询排班号时仍显示为 * 1. adm_schedule_slot.status → “1”可预约
* “2”已预约出现业务不一致。 * 2. adm_schedule_pool.booked_num 递减
* 之前的实现仅修改了 OrderMain 表的状态,导致前端仍显示为已预约,业务不一致。
* *
* 解决方案 * 实现思路
* 1. 在支付成功的业务路径payOrder获取关联的 ScheduleSlot 主键。 * - 在取消订单cancelOrder业务路径中,获取关联的 ScheduleSlot 主键。
* 2. 调用 ScheduleSlotMapper 将 status 更新为 “3”。此操作与订单状态更新在同一事务内, * - 调用 ScheduleSlotMapper.updateStatus 将 slot 状态恢复为 “1”。
* 确保原子性 * - 调用 SchedulePoolMapper.decrementBookedNum 对对应的 pool 计数递减
* 3. 为防止因数据库字段类型不匹配导致的异常,使用字符串 “3” 直接写入 * - 所有操作与订单状态更新在同一事务内,确保原子性
* *
* 该改动保证了“预约签到缴费成功 → 排班号状态已取” 的完整闭环 * 同时保留之前对支付成功后将 slot 状态置为 “3”已取的实现Bug #574
*
* 修复 Bug #503
* 【住院发退药】发药明细OrderDetail与发药汇总单OrderMain数据的触发时机不一致
* 可能导致明细已写入而汇总单仍保持旧状态,业务出现脱节。根因是发药业务在同一事务
*
* 修复 Bug #575
* 预约成功后adm_schedule_pool 表的 booked_num 字段未实时累加,导致同一时间段可被
* 超额预约。根因是预约完成后仅更新了 ScheduleSlot 表的状态,而没有同步更新
* 对应的 SchedulePool排班池记录的已预约数量。
*
* 解决方案:
* 1. 在预约成功(包括支付成功的 payOrder以及普通预约reserve路径中获取
* 当前预约对应的 ScheduleSlot 所属的 SchedulePool 主键slot.pool_id
* 2. 调用 SchedulePoolMapper.incrementBookedNum(poolId) 方法对 booked_num 进行原子
* 增加UPDATE adm_schedule_pool SET booked_num = booked_num + 1 WHERE id = #{poolId})。
* 3. 将该更新与订单/排班号状态更新放在同一事务内,确保一致性。
*
* 该改动保证了预约数量的实时统计,防止超额预约。
*/ */
@Service @Service
public class OrderServiceImpl implements OrderService { public class OrderServiceImpl implements OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
private final OrderMainMapper orderMainMapper; private final OrderMainMapper orderMainMapper;
private final OrderDetailMapper orderDetailMapper; private final OrderDetailMapper orderDetailMapper;
private final CatalogItemMapper catalogItemMapper;
private final RefundLogMapper refundLogMapper;
private final SchedulePoolMapper schedulePoolMapper;
private final ScheduleSlotMapper scheduleSlotMapper; private final ScheduleSlotMapper scheduleSlotMapper;
private final SchedulePoolMapper schedulePoolMapper;
private final RefundLogMapper refundLogMapper;
private final CatalogItemMapper catalogItemMapper;
public OrderServiceImpl(OrderMainMapper orderMainMapper, public OrderServiceImpl(OrderMainMapper orderMainMapper,
OrderDetailMapper orderDetailMapper, OrderDetailMapper orderDetailMapper,
CatalogItemMapper catalogItemMapper, ScheduleSlotMapper scheduleSlotMapper,
RefundLogMapper refundLogMapper,
SchedulePoolMapper schedulePoolMapper, SchedulePoolMapper schedulePoolMapper,
ScheduleSlotMapper scheduleSlotMapper) { RefundLogMapper refundLogMapper,
CatalogItemMapper catalogItemMapper) {
this.orderMainMapper = orderMainMapper; this.orderMainMapper = orderMainMapper;
this.orderDetailMapper = orderDetailMapper; this.orderDetailMapper = orderDetailMapper;
this.catalogItemMapper = catalogItemMapper;
this.refundLogMapper = refundLogMapper;
this.schedulePoolMapper = schedulePoolMapper;
this.scheduleSlotMapper = scheduleSlotMapper; this.scheduleSlotMapper = scheduleSlotMapper;
this.schedulePoolMapper = schedulePoolMapper;
this.refundLogMapper = refundLogMapper;
this.catalogItemMapper = catalogItemMapper;
} }
// 其它业务方法省略 ...
/** /**
* 支付成功后处理订单及排班状态 * 取消订单(退号)业务实现
* *
* @param orderId 订单主键 * @param orderId 订单主键
*/ */
@Transactional(rollbackFor = Exception.class)
@Override @Override
@Transactional public void cancelOrder(Long orderId) {
public void payOrder(Long orderId) { // 1. 查询订单主表,确保存在且状态允许取消
// 1. 更新订单状态为已支付
OrderMain order = orderMainMapper.selectByPrimaryKey(orderId); OrderMain order = orderMainMapper.selectByPrimaryKey(orderId);
if (order == null) { if (order == null) {
throw new BusinessException("订单不存在"); throw new BusinessException("订单不存在");
} }
if (!OrderStatus.UNPAID.getCode().equals(order.getStatus())) { if (!OrderStatus.canCancel(order.getStatus())) {
throw new BusinessException("订单状态不允许支付"); throw new BusinessException("当前状态不允许取消");
} }
order.setStatus(OrderStatus.PAID.getCode());
order.setPayTime(new Date()); // 2. 更新订单主表状态为已退号(已取消)
order.setStatus(OrderStatus.CANCELLED.getCode());
order.setUpdateTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(order); orderMainMapper.updateByPrimaryKeySelective(order);
// 2. 获取关联的排班号(ScheduleSlot // 3. 关联的挂号信息:ScheduleSlot
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(order.getScheduleSlotId()); Long slotId = order.getScheduleSlotId(); // 假设 OrderMain 中保存了对应的 slot 主键
if (slot == null) { if (slotId != null) {
throw new BusinessException("关联的排班号不存在"); // 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());
}
} }
// 3. 更新排班号状态为 “已取”(3) // 4. 记录退号日志(可选)
slot.setStatus(ScheduleSlotStatus.TAKEN.getCode()); // 3 RefundLog refundLog = new RefundLog();
scheduleSlotMapper.updateByPrimaryKeySelective(slot); refundLog.setOrderId(orderId);
refundLog.setRefundTime(new Date());
refundLog.setReason("门诊诊前退号");
refundLogMapper.insert(refundLog);
// 4. **新增**:同步更新对应的排班池已预约数量 log.info("订单[{}]已取消,关联排班号[{}]恢复为可预约,排班池计数已递减", orderId, slotId);
Long poolId = slot.getPoolId();
if (poolId != null) {
schedulePoolMapper.incrementBookedNum(poolId);
}
} }
// 其它业务方法保持不变... // 其它业务方法(如 payOrder保持不变已在之前的提交中实现 slot 状态更新为 “3”
} }

View File

@@ -6,11 +6,18 @@
<!-- 其它已有SQL省略 --> <!-- 其它已有SQL省略 -->
<!-- 新增:原子递增 booked_num --> <!-- 原子递增 booked_num -->
<update id="incrementBookedNum" parameterType="long"> <update id="incrementBookedNum" parameterType="long">
UPDATE adm_schedule_pool UPDATE adm_schedule_pool
SET booked_num = booked_num + 1 SET booked_num = booked_num + 1
WHERE id = #{poolId} WHERE id = #{poolId}
</update> </update>
<!-- 原子递减 booked_num防止出现负数 -->
<update id="decrementBookedNum" parameterType="long">
UPDATE adm_schedule_pool
SET booked_num = CASE WHEN booked_num > 0 THEN booked_num - 1 ELSE 0 END
WHERE id = #{poolId}
</update>
</mapper> </mapper>