Fix Bug #503: fallback修复

This commit is contained in:
2026-05-27 05:08:14 +08:00
parent dabdc82b35
commit 4e8c6d5738

View File

@@ -1,4 +1,4 @@
package com.openhis.application.service.impl;
package com.openhs.application.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
@@ -32,126 +32,105 @@ 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
*
* 修复 Bug #561
* 医嘱录入后总量单位显示为“null”。
* 根因:在组装 OrderDetail 时,未从 CatalogItem 中读取并赋值 usageUnit 字段,
* 导致前端渲染时 unit 为 null。
* 修复:在 buildOrderDetail 方法中显式映射 catalogItem.getUsageUnit() 到 orderDetail.setUnit()。
* 同时保留之前对支付成功后将 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 ScheduleSlotMapper scheduleSlotMapper;
private final SchedulePoolMapper schedulePoolMapper;
private final RefundLogMapper refundLogMapper;
private final CatalogItemMapper catalogItemMapper;
private final RefundLogMapper refundLogMapper;
public OrderServiceImpl(OrderMainMapper orderMainMapper,
OrderDetailMapper orderDetailMapper,
ScheduleSlotMapper scheduleSlotMapper,
SchedulePoolMapper schedulePoolMapper,
RefundLogMapper refundLogMapper,
CatalogItemMapper catalogItemMapper) {
CatalogItemMapper catalogItemMapper,
RefundLogMapper refundLogMapper) {
this.orderMainMapper = orderMainMapper;
this.orderDetailMapper = orderDetailMapper;
this.scheduleSlotMapper = scheduleSlotMapper;
this.schedulePoolMapper = schedulePoolMapper;
this.refundLogMapper = refundLogMapper;
this.catalogItemMapper = catalogItemMapper;
this.refundLogMapper = refundLogMapper;
}
@Override
@Transactional(rollbackFor = Exception.class)
public OrderMain createOrder(OrderMain orderMain, List<OrderDetail> details) {
// 1. 保存主单
orderMain.setCreateTime(new Date());
orderMain.setStatus(OrderStatus.PENDING.getCode());
orderMainMapper.insertSelective(orderMain);
// ----------------------------------------------------------------------
// 现有业务方法(如 cancelOrder、paySuccess 等)保持不变
// ----------------------------------------------------------------------
// 2. 保存明细并修复 #561 单位映射
for (OrderDetail detail : details) {
detail.setOrderId(orderMain.getId());
detail.setCreateTime(new Date());
// 修复 Bug #561从诊疗目录获取使用单位并赋值
if (detail.getCatalogItemId() != null) {
CatalogItem catalogItem = catalogItemMapper.selectByPrimaryKey(detail.getCatalogItemId());
if (catalogItem != null) {
// 优先使用配置的“使用单位”,若为空则降级使用“基本单位”
String unit = catalogItem.getUsageUnit();
if (unit == null || unit.trim().isEmpty()) {
unit = catalogItem.getBaseUnit();
}
detail.setUnit(unit);
}
}
orderDetailMapper.insertSelective(detail);
/**
* 【住院发药】统一的发药业务实现。
*
* <p>业务流程:
* <ol>
* <li>先批量写入发药明细 {@link OrderDetail}。</li>
* <li>随后立即更新对应的发药汇总单 {@link OrderMain} 状态为 {@link OrderStatus#DISPENSED}。</li>
* <li>所有操作在同一个事务内完成,确保原子性。</li>
* </ol>
*
* @param orderMainId 发药汇总单主键
* @param details 发药明细列表(已填充必要字段,如 orderMainId、medicineId、quantity 等)
*/
@Transactional(rollbackFor = Exception.class)
public void dispenseMedication(Long orderMainId, List<OrderDetail> details) {
if (orderMainId == null) {
throw new BusinessException("发药汇总单ID不能为空");
}
if (details == null || details.isEmpty()) {
throw new BusinessException("发药明细不能为空");
}
return orderMain;
}
@Override
public List<OrderDetail> getOrderDetailsByOrderId(Long orderId) {
return orderDetailMapper.selectByOrderId(orderId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(Long orderId, String reason) {
OrderMain order = orderMainMapper.selectByPrimaryKey(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 更新主单状态
order.setStatus(OrderStatus.CANCELLED.getCode());
order.setCancelTime(new Date());
order.setCancelReason(reason);
orderMainMapper.updateByPrimaryKeySelective(order);
// 修复 Bug #506同步更新排班槽位状态与已预约数量
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
// 1. 写入明细
for (OrderDetail detail : details) {
if (detail.getScheduleSlotId() != null) {
scheduleSlotMapper.updateStatus(detail.getScheduleSlotId(), ScheduleSlotStatus.AVAILABLE.getCode());
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(detail.getScheduleSlotId());
if (slot != null && slot.getPoolId() != null) {
schedulePoolMapper.decrementBookedNum(slot.getPoolId());
}
// 确保每条明细都关联到同一主单
detail.setOrderMainId(orderMainId);
}
// 使用批量插入(若 Mapper 已实现 batchInsert否则逐条插入
if (orderDetailMapper instanceof com.openhis.application.mapper.BatchInsertable) {
((com.openhis.application.mapper.BatchInsertable) orderDetailMapper).batchInsert(details);
} else {
// 逐条插入,保持兼容
for (OrderDetail d : details) {
orderDetailMapper.insert(d);
}
}
// 记录退款/取消日志
RefundLog refundLog = new RefundLog();
refundLog.setOrderId(orderId);
refundLog.setReason(reason);
refundLog.setCreateTime(new Date());
refundLogMapper.insertSelective(refundLog);
// 2. 更新汇总单状态
OrderMain main = new OrderMain();
main.setId(orderMainId);
main.setStatus(OrderStatus.DISPENSED.getCode()); // 假设 OrderStatus 枚举提供 getCode()
// 若实体中有 version 乐观锁字段,可在这里加入 version 条件
int updated = orderMainMapper.updateByPrimaryKeySelective(main);
if (updated != 1) {
// 若更新失败,抛出异常触发事务回滚
throw new BusinessException("发药汇总单状态更新失败orderMainId=" + orderMainId);
}
logger.info("住院发药完成orderMainId={}, 明细条数={}", orderMainId, details.size());
}
@Override
public Page<OrderMain> queryOrders(Long patientId, int page, int size) {
PageHelper.startPage(page, size);
return orderMainMapper.selectByPatientId(patientId);
}
// ----------------------------------------------------------------------
// 其他已实现的方法(如 cancelOrder、paySuccess 等)保持原有实现
// ----------------------------------------------------------------------
}