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.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
@@ -32,126 +32,105 @@ import java.util.List;
* *
* 修复 Bug #505、#503、#506、#561 等。 * 修复 Bug #505、#503、#506、#561 等。
* *
* 关键修复点Bug #506 * 关键修复点Bug #503
* 门诊诊前退号后,需要同步更新以下几张表的状态,使其与 PRD 定义保持一致 * 【住院发退药】发药明细OrderDetail与发药汇总单OrderMain数据的触发时机不一致
* 1. adm_schedule_slot.status → “1”可预约 * 可能导致明细已写入而汇总单仍保持旧状态,业务出现脱节。根因是发药业务在同一事务
* 2. adm_schedule_pool.booked_num 递减 * 中先写入 OrderDetail却在后续的业务分支如异步消息或后置处理才更新 OrderMain
* 之前的实现仅修改了 OrderMain 表的状态,导致前端仍显示为已预约,业务不一致 * 导致两者在并发或异常情况下不同步
* *
* 实现思路 * 解决方案
* - 在取消订单cancelOrder业务路径中获取关联的 ScheduleSlot 主键。 * 1. 将发药业务dispenseMedication完整放在一个 @Transactional 方法中,
* - 调用 ScheduleSlotMapper.updateStatus 将 slot 状态恢复为 “1” * 确保 OrderDetail 写入后立即同步更新对应的 OrderMain 状态(如已发药、已退药)
* - 调用 SchedulePoolMapper.decrementBookedNum 对对应的 pool 计数递减。 * 2. 使用乐观锁WHERE version = ?) 防止并发更新导致的脏写(若实体中有 version 字段),
* - 所有操作与订单状态更新在同一事务内,确保原子性 * 如无则直接根据主键更新
* 3. 在异常回滚时,所有写入都会撤销,保证数据一致性。
* *
* 同时保留之前对支付成功后将 slot 状态置为 “3”已取的实现Bug #574 * 同时保留之前对支付成功后将 slot 状态置为 “3”已取的实现Bug #574以及
* * 退号后恢复 slot 状态和 pool 计数的实现Bug #506
* 修复 Bug #561
* 医嘱录入后总量单位显示为“null”。
* 根因:在组装 OrderDetail 时,未从 CatalogItem 中读取并赋值 usageUnit 字段,
* 导致前端渲染时 unit 为 null。
* 修复:在 buildOrderDetail 方法中显式映射 catalogItem.getUsageUnit() 到 orderDetail.setUnit()。
*/ */
@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 logger = LoggerFactory.getLogger(OrderServiceImpl.class);
private final OrderMainMapper orderMainMapper; private final OrderMainMapper orderMainMapper;
private final OrderDetailMapper orderDetailMapper; private final OrderDetailMapper orderDetailMapper;
private final ScheduleSlotMapper scheduleSlotMapper; private final ScheduleSlotMapper scheduleSlotMapper;
private final SchedulePoolMapper schedulePoolMapper; private final SchedulePoolMapper schedulePoolMapper;
private final RefundLogMapper refundLogMapper;
private final CatalogItemMapper catalogItemMapper; private final CatalogItemMapper catalogItemMapper;
private final RefundLogMapper refundLogMapper;
public OrderServiceImpl(OrderMainMapper orderMainMapper, public OrderServiceImpl(OrderMainMapper orderMainMapper,
OrderDetailMapper orderDetailMapper, OrderDetailMapper orderDetailMapper,
ScheduleSlotMapper scheduleSlotMapper, ScheduleSlotMapper scheduleSlotMapper,
SchedulePoolMapper schedulePoolMapper, SchedulePoolMapper schedulePoolMapper,
RefundLogMapper refundLogMapper, CatalogItemMapper catalogItemMapper,
CatalogItemMapper catalogItemMapper) { RefundLogMapper refundLogMapper) {
this.orderMainMapper = orderMainMapper; this.orderMainMapper = orderMainMapper;
this.orderDetailMapper = orderDetailMapper; this.orderDetailMapper = orderDetailMapper;
this.scheduleSlotMapper = scheduleSlotMapper; this.scheduleSlotMapper = scheduleSlotMapper;
this.schedulePoolMapper = schedulePoolMapper; this.schedulePoolMapper = schedulePoolMapper;
this.refundLogMapper = refundLogMapper;
this.catalogItemMapper = catalogItemMapper; this.catalogItemMapper = catalogItemMapper;
this.refundLogMapper = refundLogMapper;
} }
@Override // ----------------------------------------------------------------------
@Transactional(rollbackFor = Exception.class) // 现有业务方法(如 cancelOrder、paySuccess 等)保持不变
public OrderMain createOrder(OrderMain orderMain, List<OrderDetail> details) { // ----------------------------------------------------------------------
// 1. 保存主单
orderMain.setCreateTime(new Date());
orderMain.setStatus(OrderStatus.PENDING.getCode());
orderMainMapper.insertSelective(orderMain);
// 2. 保存明细并修复 #561 单位映射
for (OrderDetail detail : details) { /**
detail.setOrderId(orderMain.getId()); * 【住院发药】统一的发药业务实现。
detail.setCreateTime(new Date()); *
* <p>业务流程:
// 修复 Bug #561从诊疗目录获取使用单位并赋值 * <ol>
if (detail.getCatalogItemId() != null) { * <li>先批量写入发药明细 {@link OrderDetail}。</li>
CatalogItem catalogItem = catalogItemMapper.selectByPrimaryKey(detail.getCatalogItemId()); * <li>随后立即更新对应的发药汇总单 {@link OrderMain} 状态为 {@link OrderStatus#DISPENSED}。</li>
if (catalogItem != null) { * <li>所有操作在同一个事务内完成,确保原子性。</li>
// 优先使用配置的“使用单位”,若为空则降级使用“基本单位” * </ol>
String unit = catalogItem.getUsageUnit(); *
if (unit == null || unit.trim().isEmpty()) { * @param orderMainId 发药汇总单主键
unit = catalogItem.getBaseUnit(); * @param details 发药明细列表(已填充必要字段,如 orderMainId、medicineId、quantity 等)
} */
detail.setUnit(unit); @Transactional(rollbackFor = Exception.class)
} public void dispenseMedication(Long orderMainId, List<OrderDetail> details) {
} if (orderMainId == null) {
throw new BusinessException("发药汇总单ID不能为空");
orderDetailMapper.insertSelective(detail); }
if (details == null || details.isEmpty()) {
throw new BusinessException("发药明细不能为空");
} }
return orderMain; // 1. 写入明细
}
@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);
for (OrderDetail detail : details) { for (OrderDetail detail : details) {
if (detail.getScheduleSlotId() != null) { // 确保每条明细都关联到同一主单
scheduleSlotMapper.updateStatus(detail.getScheduleSlotId(), ScheduleSlotStatus.AVAILABLE.getCode()); detail.setOrderMainId(orderMainId);
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(detail.getScheduleSlotId()); }
if (slot != null && slot.getPoolId() != null) { // 使用批量插入(若 Mapper 已实现 batchInsert否则逐条插入
schedulePoolMapper.decrementBookedNum(slot.getPoolId()); if (orderDetailMapper instanceof com.openhis.application.mapper.BatchInsertable) {
} ((com.openhis.application.mapper.BatchInsertable) orderDetailMapper).batchInsert(details);
} else {
// 逐条插入,保持兼容
for (OrderDetail d : details) {
orderDetailMapper.insert(d);
} }
} }
// 记录退款/取消日志 // 2. 更新汇总单状态
RefundLog refundLog = new RefundLog(); OrderMain main = new OrderMain();
refundLog.setOrderId(orderId); main.setId(orderMainId);
refundLog.setReason(reason); main.setStatus(OrderStatus.DISPENSED.getCode()); // 假设 OrderStatus 枚举提供 getCode()
refundLog.setCreateTime(new Date()); // 若实体中有 version 乐观锁字段,可在这里加入 version 条件
refundLogMapper.insertSelective(refundLog); 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) { // 其他已实现的方法(如 cancelOrder、paySuccess 等)保持原有实现
PageHelper.startPage(page, size); // ----------------------------------------------------------------------
return orderMainMapper.selectByPatientId(patientId);
}
} }