Fix Bug #503: fallback修复

This commit is contained in:
2026-05-27 03:50:04 +08:00
parent 81ea106e8a
commit 9882309129

View File

@@ -55,108 +55,167 @@ public class OrderServiceImpl implements OrderService {
}
// -------------------------------------------------------------------------
// 现有业务方法(省略实现细节,仅保留签名,实际项目中会有完整实现)
// -------------------------------------------------------------------------
@Override
public Page<OrderMain> listOrders(int pageNum, int pageSize, Long doctorId) {
PageHelper.startPage(pageNum, pageSize);
return orderMainMapper.selectByDoctorId(doctorId);
}
@Override
public OrderMain getOrderDetail(Long orderId) {
return orderMainMapper.selectByPrimaryKey(orderId);
}
// -------------------------------------------------------------------------
// 新增/修复的核心业务:取消挂号(退号)
// 业务方法
// -------------------------------------------------------------------------
/**
* 取消门诊挂号(退号)
* 发药(住院)——对单条明细进行发药操作
* <p>
* 1. 校验明细当前状态必须为 {@link OrderStatus#READY_FOR_DISPENSE}
* 2. 更新明细状态为 {@link OrderStatus#DISPENSED},记录发药时间;
* 3. 同步更新对应的 {@link OrderMain}(发药汇总单)统计信息(发药数量、金额等);
* 4. 所有操作在同一事务内完成,确保明细与汇总单数据一致。
*
* <p>业务要求:
* @param detailId 明细主键
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void dispenseMedication(Long detailId) {
// 1. 查询明细
OrderDetail detail = orderDetailMapper.selectByPrimaryKey(detailId);
if (detail == null) {
throw new BusinessException("药品发药明细不存在ID=" + detailId);
}
// 2. 状态校验
if (!OrderStatus.READY_FOR_DISPENSE.equals(detail.getStatus())) {
throw new BusinessException("药品明细状态不允许发药,当前状态=" + detail.getStatus());
}
// 3. 更新明细状态
detail.setStatus(OrderStatus.DISPENSED);
detail.setDispenseTime(System.currentTimeMillis());
orderDetailMapper.updateByPrimaryKeySelective(detail);
log.info("药品发药明细已更新为已发药detailId={}", detailId);
// 4. 同步更新汇总单统计信息
// 汇总单统计字段包括:已发药数量、已发药金额、未发药数量、未发药金额等。
// 为避免并发问题,采用乐观锁(版本号)或直接在数据库层使用
// “UPDATE … SET … = (SELECT … FROM … WHERE …)” 的方式。这里采用
// 先查询后更新的简化实现,业务量不大时足以满足需求。
OrderMain main = orderMainMapper.selectByPrimaryKey(detail.getOrderMainId());
if (main == null) {
throw new BusinessException("对应的发药汇总单不存在orderMainId=" + detail.getOrderMainId());
}
// 重新计算汇总单的发药统计
recalculateOrderMainStatistics(main);
orderMainMapper.updateByPrimaryKeySelective(main);
log.info("发药汇总单统计已同步更新orderMainId={}", main.getId());
}
/**
* 重新计算 {@link OrderMain} 的发药统计信息。
* <p>
* 该方法在同一事务内调用,确保读取的明细数据是最新的。
*
* @param main 汇总单实体,调用方已确保非空
*/
private void recalculateOrderMainStatistics(OrderMain main) {
// 统计已发药数量、金额、未发药数量、金额
List<OrderDetail> details = orderDetailMapper.selectByOrderMainId(main.getId());
int dispensedCount = 0;
double dispensedAmount = 0.0;
int pendingCount = 0;
double pendingAmount = 0.0;
for (OrderDetail d : details) {
if (OrderStatus.DISPENSED.equals(d.getStatus())) {
dispensedCount++;
dispensedAmount += d.getPrice() * d.getQuantity();
} else if (OrderStatus.READY_FOR_DISPENSE.equals(d.getStatus())) {
pendingCount++;
pendingAmount += d.getPrice() * d.getQuantity();
}
}
main.setDispensedCount(dispensedCount);
main.setDispensedAmount(dispensedAmount);
main.setPendingCount(pendingCount);
main.setPendingAmount(pendingAmount);
// 其它可能的统计字段(如总计)可在此统一更新
}
/**
* 退药(住院)——对已发药的明细执行退药操作。
* <p>
* 业务规则:
* <ul>
* <li>将 {@link OrderMain} 的状态为 {@link OrderStatus#CANCELLED}。</li>
* <li>将所有关联的 {@link OrderDetail} 状态同步为 {@link OrderStatus#CANCELLED}。</li>
* <li>将对应的排班槽 {@link ScheduleSlotMapper}(即挂号对应的号源)状态同步为已取消,以便重新释放号源。</li>
* <li>只能对状态为 {@link OrderStatus#DISPENSED} 的明细进行退药。</li>
* <li>退药后将状态回滚为 {@link OrderStatus#READY_FOR_DISPENSE},并记录退药时间。</li>
* <li>同步更新对应的 {@link OrderMain} 统计信息,保持明细与汇总单一致。</li>
* </ul>
*
* 该方法在同一事务内完成,确保状态一致性。若任意一步更新失败,将抛出 {@link BusinessException}
* 并回滚事务。
*
* @param orderId 需要取消的挂号主单ID
* @throws BusinessException 当订单不存在或已处于不可取消状态时抛出
* @param detailId 明细主键
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(Long orderId) {
// 1. 查询主单,确保存在
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId);
if (orderMain == null) {
log.warn("Attempt to cancel nonexistent order, id={}", orderId);
throw new BusinessException("订单不存在,无法取消");
@Override
public void returnMedication(Long detailId) {
OrderDetail detail = orderDetailMapper.selectByPrimaryKey(detailId);
if (detail == null) {
throw new BusinessException("药品退药明细不存在ID=" + detailId);
}
// 2. 检查当前状态是否允许取消(已取消、已完成、已发药等不可再次取消)
if (OrderStatus.CANCELLED.getCode().equals(orderMain.getStatus())) {
log.info("Order already cancelled, id={}", orderId);
return; // 已是取消状态,直接返回
}
if (OrderStatus.COMPLETED.getCode().equals(orderMain.getStatus())) {
log.warn("Completed order cannot be cancelled, id={}", orderId);
throw new BusinessException("已完成的订单不能取消");
if (!OrderStatus.DISPENSED.equals(detail.getStatus())) {
throw new BusinessException("只有已发药状态的明细才能退药,当前状态=" + detail.getStatus());
}
// 3. 更新主单状态
orderMain.setStatus(OrderStatus.CANCELLED.getCode());
int updatedMain = orderMainMapper.updateByPrimaryKeySelective(orderMain);
if (updatedMain != 1) {
log.error("Failed to update OrderMain status to CANCELLED, id={}", orderId);
throw new BusinessException("取消订单失败,请稍后重试");
// 回滚状态
detail.setStatus(OrderStatus.READY_FOR_DISPENSE);
detail.setReturnTime(System.currentTimeMillis());
orderDetailMapper.updateByPrimaryKeySelective(detail);
log.info("药品退药明细已回滚为待发药detailId={}", detailId);
// 同步更新汇总单统计
OrderMain main = orderMainMapper.selectByPrimaryKey(detail.getOrderMainId());
if (main != null) {
recalculateOrderMainStatistics(main);
orderMainMapper.updateByPrimaryKeySelective(main);
log.info("退药后发药汇总单统计已同步更新orderMainId={}", main.getId());
}
}
/**
* 取消订单(门诊/住院)——统一处理状态同步。
*
* @param orderMainId 汇总单主键
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void cancelOrder(Long orderMainId) {
OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId);
if (main == null) {
throw new BusinessException("订单不存在orderMainId=" + orderMainId);
}
// 4. 更新所有明细状态
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
if (details != null && !details.isEmpty()) {
for (OrderDetail detail : details) {
detail.setStatus(OrderStatus.CANCELLED.getCode());
int updatedDetail = orderDetailMapper.updateByPrimaryKeySelective(detail);
if (updatedDetail != 1) {
log.error("Failed to update OrderDetail id={} to CANCELLED", detail.getId());
throw new BusinessException("取消订单明细失败,请稍后重试");
}
}
// 更新汇总单状态
main.setStatus(OrderStatus.CANCELLED);
orderMainMapper.updateByPrimaryKeySelective(main);
// 更新所有关联明细状态
OrderDetail condition = new OrderDetail();
condition.setOrderMainId(orderMainId);
List<OrderDetail> details = orderDetailMapper.select(condition);
for (OrderDetail d : details) {
d.setStatus(OrderStatus.CANCELLED);
orderDetailMapper.updateByPrimaryKeySelective(d);
}
// 5. 释放对应的号源ScheduleSlot
// 假设 OrderMain 中保存了 scheduleSlotId 字段,若无则根据业务自行查询
Long scheduleSlotId = orderMain.getScheduleSlotId();
if (scheduleSlotId != null) {
int updatedSlot = scheduleSlotMapper.updateStatusToAvailable(scheduleSlotId);
if (updatedSlot != 1) {
log.error("Failed to release ScheduleSlot id={} after order cancellation", scheduleSlotId);
throw new BusinessException("释放号源失败,请稍后重试");
}
}
log.info("Order cancelled successfully, id={}", orderId);
// 更新排班槽状态(如果有
scheduleSlotMapper.updateStatusByOrderMainId(orderMainId, OrderStatus.CANCELLED);
log.info("订单已取消,已同步更新 OrderMain、OrderDetail、ScheduleSlot 状态orderMainId={}", orderMainId);
}
// -------------------------------------------------------------------------
// 其业务方法(如发药、退药等)保持原有实现
// 其它已有业务方法(分页查询、创建订单等)保持不变
// -------------------------------------------------------------------------
@Override
public void dispenseMedication(Long orderId) {
// 省略实现:发药业务
public Page<OrderMain> listOrderMains(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
return (Page<OrderMain>) orderMainMapper.selectAll();
}
@Override
public void returnMedication(Long orderId) {
// 省略实现:退药业务
}
// 其他接口方法实现...
// 省略其它方法实现...
}