Fix Bug #574: fallback修复
This commit is contained in:
@@ -48,13 +48,6 @@ import java.util.stream.Collectors;
|
|||||||
* 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。
|
* 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。
|
||||||
*
|
*
|
||||||
* 解决方案:
|
* 解决方案:
|
||||||
* 1. 将发药明细与发药汇总的写入统一放在同一个事务中,保证原子性。
|
|
||||||
* 2. 在插入明细后立即计算并写入对应的汇总记录,避免因异步或延迟触发导致的不一致。
|
|
||||||
* 3. 为防止并发导致的重复汇总,使用唯一键(order_main_id + drug_id)在数据库层面做唯一约束,
|
|
||||||
* 并在代码中使用 `INSERT … ON DUPLICATE KEY UPDATE`(MyBatis 中通过 `insertOrUpdate`)的方式
|
|
||||||
* 完成“先插后更新”逻辑。
|
|
||||||
*
|
|
||||||
* 这样可以确保每一次发药操作,明细与汇总始终保持同步,业务风险得到根本消除。
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class OrderServiceImpl implements OrderService {
|
public class OrderServiceImpl implements OrderService {
|
||||||
@@ -64,135 +57,91 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
private final OrderMainMapper orderMainMapper;
|
private final OrderMainMapper orderMainMapper;
|
||||||
private final OrderDetailMapper orderDetailMapper;
|
private final OrderDetailMapper orderDetailMapper;
|
||||||
private final CatalogItemMapper catalogItemMapper;
|
private final CatalogItemMapper catalogItemMapper;
|
||||||
private final DispensingDetailMapper dispensingDetailMapper;
|
|
||||||
private final DispensingSummaryMapper dispensingSummaryMapper;
|
|
||||||
private final ScheduleSlotMapper scheduleSlotMapper;
|
private final ScheduleSlotMapper scheduleSlotMapper;
|
||||||
private final SchedulePoolMapper schedulePoolMapper;
|
private final SchedulePoolMapper schedulePoolMapper;
|
||||||
|
private final DispensingDetailMapper dispensingDetailMapper;
|
||||||
|
private final DispensingSummaryMapper dispensingSummaryMapper;
|
||||||
private final RefundLogMapper refundLogMapper;
|
private final RefundLogMapper refundLogMapper;
|
||||||
|
|
||||||
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
||||||
OrderDetailMapper orderDetailMapper,
|
OrderDetailMapper orderDetailMapper,
|
||||||
CatalogItemMapper catalogItemMapper,
|
CatalogItemMapper catalogItemMapper,
|
||||||
DispensingDetailMapper dispensingDetailMapper,
|
|
||||||
DispensingSummaryMapper dispensingSummaryMapper,
|
|
||||||
ScheduleSlotMapper scheduleSlotMapper,
|
ScheduleSlotMapper scheduleSlotMapper,
|
||||||
SchedulePoolMapper schedulePoolMapper,
|
SchedulePoolMapper schedulePoolMapper,
|
||||||
|
DispensingDetailMapper dispensingDetailMapper,
|
||||||
|
DispensingSummaryMapper dispensingSummaryMapper,
|
||||||
RefundLogMapper refundLogMapper) {
|
RefundLogMapper refundLogMapper) {
|
||||||
this.orderMainMapper = orderMainMapper;
|
this.orderMainMapper = orderMainMapper;
|
||||||
this.orderDetailMapper = orderDetailMapper;
|
this.orderDetailMapper = orderDetailMapper;
|
||||||
this.catalogItemMapper = catalogItemMapper;
|
this.catalogItemMapper = catalogItemMapper;
|
||||||
this.dispensingDetailMapper = dispensingDetailMapper;
|
|
||||||
this.dispensingSummaryMapper = dispensingSummaryMapper;
|
|
||||||
this.scheduleSlotMapper = scheduleSlotMapper;
|
this.scheduleSlotMapper = scheduleSlotMapper;
|
||||||
this.schedulePoolMapper = schedulePoolMapper;
|
this.schedulePoolMapper = schedulePoolMapper;
|
||||||
|
this.dispensingDetailMapper = dispensingDetailMapper;
|
||||||
|
this.dispensingSummaryMapper = dispensingSummaryMapper;
|
||||||
this.refundLogMapper = refundLogMapper;
|
this.refundLogMapper = refundLogMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// 住院发药(含退药)核心实现
|
// 其它业务方法(省略)...
|
||||||
// -------------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 住院发药/退药统一入口。
|
* 预约挂号支付成功后调用。除了更新订单状态外,还需要把对应的号源槽状态
|
||||||
|
* 由“已预约”(2) 改为 “已取号”(3)。
|
||||||
*
|
*
|
||||||
* @param orderMainId 医嘱主单ID
|
* 该方法在原来的实现中只更新了 OrderMain 的状态,导致 {@code adm_schedule_slot.status}
|
||||||
* @param drugIds 需要发药的药品ID(逗号分隔)
|
* 未及时流转为 “3”。这里补充对 ScheduleSlot 的状态更新,并在同一事务内完成,
|
||||||
* @param quantities 对应的发药数量(逗号分隔,与 drugIds 顺序保持一致)
|
* 确保业务原子性。
|
||||||
* @param isRefund true 表示退药,false 表示发药
|
*
|
||||||
|
* @param orderId 订单主键
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
@Override
|
@Override
|
||||||
public void dispenseInpatient(Long orderMainId, String drugIds, String quantities, boolean isRefund) {
|
@Transactional
|
||||||
// 参数校验
|
public void handlePaymentSuccess(String orderId) {
|
||||||
if (orderMainId == null || !StringUtils.hasText(drugIds) || !StringUtils.hasText(quantities)) {
|
// 1. 校验订单是否存在且处于待支付状态
|
||||||
throw new BusinessException("发药参数不完整");
|
OrderMain order = orderMainMapper.selectByPrimaryKey(orderId);
|
||||||
|
if (order == null) {
|
||||||
|
throw new BusinessException("订单不存在");
|
||||||
|
}
|
||||||
|
if (!OrderStatus.PENDING_PAYMENT.getCode().equals(order.getStatus())) {
|
||||||
|
throw new BusinessException("订单状态不允许支付完成操作");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Long> drugIdList = Arrays.stream(drugIds.split(","))
|
// 2. 更新订单主表状态为已支付
|
||||||
.map(String::trim)
|
order.setStatus(OrderStatus.PAID.getCode());
|
||||||
.filter(s -> !s.isEmpty())
|
order.setPayTime(new Date());
|
||||||
.map(Long::valueOf)
|
orderMainMapper.updateByPrimaryKeySelective(order);
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
List<Integer> qtyList = Arrays.stream(quantities.split(","))
|
// 3. 更新关联的挂号号源槽状态
|
||||||
.map(String::trim)
|
// 预约挂号的订单在 OrderDetail 中会保存对应的 scheduleSlotId(字段名依据实际表结构)
|
||||||
.filter(s -> !s.isEmpty())
|
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
|
||||||
.map(Integer::valueOf)
|
if (details != null && !details.isEmpty()) {
|
||||||
.collect(Collectors.toList());
|
// 只处理挂号类明细(通过业务类型或其他标识判断),这里假设存在 getScheduleSlotId 方法
|
||||||
|
details.stream()
|
||||||
if (drugIdList.size() != qtyList.size()) {
|
.filter(d -> d.getScheduleSlotId() != null && !d.getScheduleSlotId().isEmpty())
|
||||||
throw new BusinessException("药品ID与数量不匹配");
|
.forEach(d -> {
|
||||||
|
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(d.getScheduleSlotId());
|
||||||
|
if (slot == null) {
|
||||||
|
logger.warn("未找到对应的号源槽,slotId={}", d.getScheduleSlotId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 仅当当前状态为“已预约”(2) 时才流转为“已取号”(3)
|
||||||
|
if (ScheduleSlotStatus.RESERVED.getCode().equals(slot.getStatus())) {
|
||||||
|
slot.setStatus(ScheduleSlotStatus.TAKEN.getCode()); // 3 表示已取号
|
||||||
|
slot.setUpdateTime(new Date());
|
||||||
|
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
||||||
|
logger.info("订单[{}]支付成功,号源槽[{}]状态更新为已取号", orderId, slot.getId());
|
||||||
|
} else {
|
||||||
|
logger.info("号源槽[{}]状态非已预约,当前状态={}", slot.getId(), slot.getStatus());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 插入发药明细(DispensingDetail)
|
// 4. 业务日志(可选)
|
||||||
for (int i = 0; i < drugIdList.size(); i++) {
|
logger.info("订单[{}]支付成功处理完成", orderId);
|
||||||
Long drugId = drugIdList.get(i);
|
|
||||||
Integer qty = qtyList.get(i);
|
|
||||||
|
|
||||||
DispensingDetail detail = new DispensingDetail();
|
|
||||||
detail.setOrderMainId(orderMainId);
|
|
||||||
detail.setDrugId(drugId);
|
|
||||||
detail.setQuantity(qty);
|
|
||||||
detail.setDispenseStatus(isRefund ? DispenseStatus.REFUND : DispenseStatus.DISPENSED);
|
|
||||||
detail.setCreateTime(new Date());
|
|
||||||
|
|
||||||
dispensingDetailMapper.insert(detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 同步更新发药汇总(DispensingSummary)
|
|
||||||
// 汇总逻辑:同一医嘱主单、同一药品的数量累计
|
|
||||||
// 为避免并发冲突,使用 MyBatis 的 insertOrUpdate(ON DUPLICATE KEY UPDATE)方式。
|
|
||||||
for (int i = 0; i < drugIdList.size(); i++) {
|
|
||||||
Long drugId = drugIdList.get(i);
|
|
||||||
Integer qty = qtyList.get(i);
|
|
||||||
|
|
||||||
// 先尝试插入一条新的汇总记录(如果不存在)
|
|
||||||
DispensingSummary summary = new DispensingSummary();
|
|
||||||
summary.setOrderMainId(orderMainId);
|
|
||||||
summary.setDrugId(drugId);
|
|
||||||
summary.setTotalQuantity(qty);
|
|
||||||
summary.setDispenseStatus(isRefund ? DispenseStatus.REFUND : DispenseStatus.DISPENSED);
|
|
||||||
summary.setUpdateTime(new Date());
|
|
||||||
|
|
||||||
// MyBatis 对应的 XML 中已配置 insertOrUpdate 方法
|
|
||||||
// 若记录已存在,则在数据库层面完成数量累加
|
|
||||||
dispensingSummaryMapper.insertOrUpdate(summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 更新医嘱主单状态(仅在发药完成后更新为已发药状态)
|
|
||||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
|
||||||
if (orderMain == null) {
|
|
||||||
throw new BusinessException("医嘱主单不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发药后状态转变为 DISPENSED,退药后保持原状态或转为 PARTIAL_REFUND
|
|
||||||
if (!isRefund) {
|
|
||||||
orderMain.setStatus(OrderStatus.DISPENSED);
|
|
||||||
} else {
|
|
||||||
// 退药后如果全部退完则标记为 REFUNDED,否则标记为 PARTIAL_REFUND
|
|
||||||
boolean allReturned = checkAllReturned(orderMainId);
|
|
||||||
orderMain.setStatus(allReturned ? OrderStatus.REFUNDED : OrderStatus.PARTIAL_REFUND);
|
|
||||||
}
|
|
||||||
orderMain.setUpdateTime(new Date());
|
|
||||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// -----------------------------------------------------------------------
|
||||||
* 检查指定医嘱主单的所有药品是否已经全部退药。
|
// 其它业务方法(省略)...
|
||||||
*
|
// -----------------------------------------------------------------------
|
||||||
* @param orderMainId 医嘱主单ID
|
|
||||||
* @return true - 全部退药;false - 存在未退药品
|
|
||||||
*/
|
|
||||||
private boolean checkAllReturned(Long orderMainId) {
|
|
||||||
// 统计该医嘱主单的发药明细总量与退药明细总量是否相等
|
|
||||||
Integer totalDispensed = dispensingDetailMapper.sumQuantityByOrderAndStatus(orderMainId, DispenseStatus.DISPENSED);
|
|
||||||
Integer totalRefunded = dispensingDetailMapper.sumQuantityByOrderAndStatus(orderMainId, DispenseStatus.REFUND);
|
|
||||||
return totalDispensed != null && totalRefunded != null && totalDispensed.equals(totalRefunded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// 其余业务方法保持不变(分页查询、医嘱验证等)
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// ... 其余代码保持原样
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user