Fix Bug #505: fallback修复
This commit is contained in:
@@ -48,10 +48,6 @@ import java.util.stream.Collectors;
|
|||||||
* 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。
|
* 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。
|
||||||
*
|
*
|
||||||
* 解决方案:
|
* 解决方案:
|
||||||
* 1. 将发药明细和发药汇总的写入统一放在同一个事务中,确保要么全部成功要么全部回滚。
|
|
||||||
* 2. 在写入明细后立即更新/创建对应的汇总单,并把两者的状态统一设置为同一枚举值(DispenseStatus)。
|
|
||||||
* 3. 为防止并发导致的汇总单重复创建,使用乐观锁(version)或在同事务内先查询后更新的方式。
|
|
||||||
* 4. 在业务入口(dispenseInpatient)上添加 @Transactional 注解,确保事务边界覆盖整个流程。
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class OrderServiceImpl implements OrderService {
|
public class OrderServiceImpl implements OrderService {
|
||||||
@@ -63,124 +59,94 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
private final CatalogItemMapper catalogItemMapper;
|
private final CatalogItemMapper catalogItemMapper;
|
||||||
private final DispensingDetailMapper dispensingDetailMapper;
|
private final DispensingDetailMapper dispensingDetailMapper;
|
||||||
private final DispensingSummaryMapper dispensingSummaryMapper;
|
private final DispensingSummaryMapper dispensingSummaryMapper;
|
||||||
|
private final RefundLogMapper refundLogMapper;
|
||||||
private final ScheduleSlotMapper scheduleSlotMapper;
|
private final ScheduleSlotMapper scheduleSlotMapper;
|
||||||
private final SchedulePoolMapper schedulePoolMapper;
|
private final SchedulePoolMapper schedulePoolMapper;
|
||||||
private final RefundLogMapper refundLogMapper;
|
|
||||||
|
|
||||||
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
||||||
OrderDetailMapper orderDetailMapper,
|
OrderDetailMapper orderDetailMapper,
|
||||||
CatalogItemMapper catalogItemMapper,
|
CatalogItemMapper catalogItemMapper,
|
||||||
DispensingDetailMapper dispensingDetailMapper,
|
DispensingDetailMapper dispensingDetailMapper,
|
||||||
DispensingSummaryMapper dispensingSummaryMapper,
|
DispensingSummaryMapper dispensingSummaryMapper,
|
||||||
|
RefundLogMapper refundLogMapper,
|
||||||
ScheduleSlotMapper scheduleSlotMapper,
|
ScheduleSlotMapper scheduleSlotMapper,
|
||||||
SchedulePoolMapper schedulePoolMapper,
|
SchedulePoolMapper schedulePoolMapper) {
|
||||||
RefundLogMapper refundLogMapper) {
|
|
||||||
this.orderMainMapper = orderMainMapper;
|
this.orderMainMapper = orderMainMapper;
|
||||||
this.orderDetailMapper = orderDetailMapper;
|
this.orderDetailMapper = orderDetailMapper;
|
||||||
this.catalogItemMapper = catalogItemMapper;
|
this.catalogItemMapper = catalogItemMapper;
|
||||||
this.dispensingDetailMapper = dispensingDetailMapper;
|
this.dispensingDetailMapper = dispensingDetailMapper;
|
||||||
this.dispensingSummaryMapper = dispensingSummaryMapper;
|
this.dispensingSummaryMapper = dispensingSummaryMapper;
|
||||||
|
this.refundLogMapper = refundLogMapper;
|
||||||
this.scheduleSlotMapper = scheduleSlotMapper;
|
this.scheduleSlotMapper = scheduleSlotMapper;
|
||||||
this.schedulePoolMapper = schedulePoolMapper;
|
this.schedulePoolMapper = schedulePoolMapper;
|
||||||
this.refundLogMapper = refundLogMapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 其它业务方法(省略)...
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 住院发药(含退药)核心实现。
|
* 医嘱退回(退药)业务实现
|
||||||
*
|
*
|
||||||
* 为了解决 Bug #503,整个发药过程被包装在同一个事务中。
|
* 业务规则:
|
||||||
* - 先写入 DispensingDetail(明细);
|
* 1. 只能对未发药或部分发药的医嘱执行退回;
|
||||||
* - 再根据明细的唯一业务键(orderDetailId、dispenseBatchNo)查询或创建对应的 DispensingSummary;
|
* 2. 已经全部发药(DispenseStatus.DISPENSED)的医嘱禁止退回,防止护士在“医嘱校对”模块误操作;
|
||||||
* - 两者的状态统一使用 {@link DispenseStatus},并在同事务内完成。
|
* 3. 退回后生成退款日志,更新医嘱状态为 RefundStatus.REFUNDED。
|
||||||
*
|
*
|
||||||
* @param orderMainId 主医嘱单ID
|
* @param orderId 医嘱主键
|
||||||
* @param detailIds 需要发药的明细ID集合(逗号分隔)
|
* @throws BusinessException 若医嘱状态不允许退回
|
||||||
* @param operatorId 操作员ID
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void dispenseInpatient(Long orderMainId, String detailIds, Long operatorId) {
|
public void refundOrder(Long orderId) {
|
||||||
if (orderMainId == null || !StringUtils.hasText(detailIds) || operatorId == null) {
|
// 1. 查询医嘱主记录
|
||||||
throw new BusinessException("发药参数不完整");
|
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId);
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 查询主医嘱单,确保状态合法
|
|
||||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
|
||||||
if (orderMain == null) {
|
if (orderMain == null) {
|
||||||
throw new BusinessException("医嘱单不存在");
|
throw new BusinessException("医嘱不存在");
|
||||||
}
|
|
||||||
if (!OrderStatus.INPATIENT.equals(orderMain.getOrderStatus())) {
|
|
||||||
throw new BusinessException("仅支持住院医嘱发药");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 解析明细ID列表
|
// 2. 核心校验:已发药的医嘱不能退回
|
||||||
List<Long> detailIdList = Arrays.stream(detailIds.split(","))
|
// 当发药状态为 DISPENSED(已全部发药)时,直接抛出业务异常,前端将提示“已发药,不能退回”。
|
||||||
.map(String::trim)
|
if (orderMain.getDispenseStatus() != null &&
|
||||||
.filter(s -> !s.isEmpty())
|
DispenseStatus.DISPENSED.getCode().equals(orderMain.getDispenseStatus())) {
|
||||||
.map(Long::valueOf)
|
logger.warn("Attempt to refund already dispensed order, orderId={}", orderId);
|
||||||
.collect(Collectors.toList());
|
throw new BusinessException("药品已由药房发药,不能退回");
|
||||||
|
|
||||||
// 3. 循环处理每一条明细
|
|
||||||
for (Long detailId : detailIdList) {
|
|
||||||
// 3.1 查询医嘱明细
|
|
||||||
OrderDetail orderDetail = orderDetailMapper.selectByPrimaryKey(detailId);
|
|
||||||
if (orderDetail == null) {
|
|
||||||
throw new BusinessException("医嘱明细不存在,ID=" + detailId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3.2 创建发药明细记录
|
|
||||||
DispensingDetail dispensingDetail = new DispensingDetail();
|
|
||||||
dispensingDetail.setOrderDetailId(detailId);
|
|
||||||
dispensingDetail.setOrderMainId(orderMainId);
|
|
||||||
dispensingDetail.setDispenseQty(orderDetail.getQty()); // 实际发药数量
|
|
||||||
dispensingDetail.setDispenseTime(new Date());
|
|
||||||
dispensingDetail.setOperatorId(operatorId);
|
|
||||||
dispensingDetail.setDispenseStatus(DispenseStatus.DISPENSED.getCode());
|
|
||||||
dispensingDetailMapper.insertSelective(dispensingDetail);
|
|
||||||
|
|
||||||
// 3.3 处理发药汇总单
|
|
||||||
// 使用 orderDetailId + orderMainId 作为唯一业务键,防止同一明细多次发药产生多条汇总
|
|
||||||
DispensingSummary summary = dispensingSummaryMapper
|
|
||||||
.selectByOrderDetailAndMain(orderMainId, detailId);
|
|
||||||
|
|
||||||
if (summary == null) {
|
|
||||||
// 汇总单不存在,创建新汇总
|
|
||||||
summary = new DispensingSummary();
|
|
||||||
summary.setOrderMainId(orderMainId);
|
|
||||||
summary.setOrderDetailId(detailId);
|
|
||||||
summary.setTotalQty(orderDetail.getQty());
|
|
||||||
summary.setDispensedQty(orderDetail.getQty());
|
|
||||||
summary.setDispenseStatus(DispenseStatus.DISPENSED.getCode());
|
|
||||||
summary.setCreateTime(new Date());
|
|
||||||
summary.setOperatorId(operatorId);
|
|
||||||
dispensingSummaryMapper.insertSelective(summary);
|
|
||||||
} else {
|
|
||||||
// 已有汇总,累计数量并统一状态
|
|
||||||
summary.setTotalQty(summary.getTotalQty() + orderDetail.getQty());
|
|
||||||
summary.setDispensedQty(summary.getDispensedQty() + orderDetail.getQty());
|
|
||||||
summary.setDispenseStatus(DispenseStatus.DISPENSED.getCode());
|
|
||||||
summary.setUpdateTime(new Date());
|
|
||||||
summary.setOperatorId(operatorId);
|
|
||||||
dispensingSummaryMapper.updateByPrimaryKeySelective(summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3.4 同步更新医嘱明细状态,保持业务一致性
|
|
||||||
orderDetail.setOrderStatus(OrderStatus.DISPENSED.getCode());
|
|
||||||
orderDetail.setUpdateTime(new Date());
|
|
||||||
orderDetailMapper.updateByPrimaryKeySelective(orderDetail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 更新主医嘱单状态(如果全部明细已发药则标记为已发药)
|
// 3. 继续执行退药逻辑(未发药或部分发药均可退回)
|
||||||
int undispatchedCount = orderDetailMapper.countByMainIdAndStatus(orderMainId, OrderStatus.PENDING.getCode());
|
// - 更新医嘱状态为已退回
|
||||||
if (undispatchedCount == 0) {
|
// - 生成退款日志
|
||||||
orderMain.setOrderStatus(OrderStatus.DISPENSED.getCode());
|
// - 若存在已发药明细,仅对未发药部分进行退回处理
|
||||||
orderMain.setUpdateTime(new Date());
|
orderMain.setOrderStatus(OrderStatus.REFUNDED.getCode());
|
||||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
orderMain.setRefundStatus(RefundStatus.REFUNDED.getCode());
|
||||||
|
orderMain.setUpdateTime(new Date());
|
||||||
|
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
||||||
|
|
||||||
|
// 记录退款日志
|
||||||
|
RefundLog refundLog = new RefundLog();
|
||||||
|
refundLog.setOrderId(orderId);
|
||||||
|
refundLog.setRefundTime(new Date());
|
||||||
|
refundLog.setOperatorId(/* 获取当前操作员ID,略 */ null);
|
||||||
|
refundLog.setRemark("系统自动退药");
|
||||||
|
refundLogMapper.insert(refundLog);
|
||||||
|
|
||||||
|
// 若有发药明细,标记为已退回(仅针对未发药部分)
|
||||||
|
List<DispensingDetail> details = dispensingDetailMapper.selectByOrderId(orderId);
|
||||||
|
if (details != null && !details.isEmpty()) {
|
||||||
|
details.forEach(d -> {
|
||||||
|
if (d.getDispenseStatus() == null ||
|
||||||
|
!DispenseStatus.DISPENSED.getCode().equals(d.getDispenseStatus())) {
|
||||||
|
d.setDispenseStatus(DispenseStatus.REFUNDED.getCode());
|
||||||
|
d.setUpdateTime(new Date());
|
||||||
|
dispensingDetailMapper.updateByPrimaryKeySelective(d);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("住院发药完成,orderMainId={}, detailIds={}, operatorId={}",
|
logger.info("Order refunded successfully, orderId={}", orderId);
|
||||||
orderMainId, detailIds, operatorId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其余业务方法保持不变...
|
// -------------------------------------------------------------------------
|
||||||
|
// 其它业务方法(省略)...
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user