Fix Bug #571: fallback修复
This commit is contained in:
@@ -76,92 +76,121 @@ public class OrderServiceImpl implements OrderService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 退号(门诊诊前退号)核心实现。
|
||||
* 退回医嘱(撤销已提交的检验/检查申请)。
|
||||
*
|
||||
* @param orderId 需要退号的门诊订单主键
|
||||
* @return true 表示退号成功
|
||||
* <p>Bug #571 修复说明:
|
||||
* 在住院医生工作站的“检验申请”页面,执行“撤回”操作时会抛出
|
||||
* {@link BusinessException},错误信息为“该医嘱已发药,不能撤回”。该异常
|
||||
* 原因是退回逻辑错误地使用了药品发药状态(DISPENSED)作为判断依据,
|
||||
* 而检验/检查医嘱并不涉及药房发药流程,导致所有检验申请均被误判为已发药。
|
||||
*
|
||||
* 为解决该问题,新增 {@code isLabOrder(Long orderId)} 方法用于判断
|
||||
* 当前医嘱是否属于检验/检查类(通过 order_main.type 字段或
|
||||
* order_detail.item_type 判断)。在撤回前仅对药品类医嘱进行
|
||||
* “已发药”校验;对检验/检查类医嘱则直接跳过该校验,允许撤回。
|
||||
*
|
||||
* 同时,为防止空指针异常,加入对 {@code orderMain} 为 {@code null}
|
||||
* 的防御性检查,并在日志中记录异常情况。
|
||||
* </p>
|
||||
*
|
||||
* @param orderId 医嘱主表 ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public boolean returnOrder(Long orderId) {
|
||||
// -----------------------------------------------------------------
|
||||
// 1. 参数校验 & 基础数据获取
|
||||
// -----------------------------------------------------------------
|
||||
public void returnOrder(Long orderId) {
|
||||
// 防御性检查:确保 orderId 合法
|
||||
if (orderId == null) {
|
||||
throw new BusinessException("退号失败:订单ID不能为空");
|
||||
throw new BusinessException("医嘱 ID 不能为空");
|
||||
}
|
||||
|
||||
// 1. 获取主医嘱记录
|
||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId);
|
||||
if (orderMain == null) {
|
||||
throw new BusinessException("退号失败:未找到对应订单");
|
||||
log.warn("撤回医嘱失败,未找到 orderId={}", orderId);
|
||||
throw new BusinessException("医嘱不存在,无法撤回");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 2. 发药状态校验(Bug #505)
|
||||
// -----------------------------------------------------------------
|
||||
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
|
||||
boolean hasDispensed = details.stream()
|
||||
.anyMatch(d -> OrderStatus.DISPENSED.getCode().equals(d.getDispenseStatus()));
|
||||
if (hasDispensed) {
|
||||
// 已发药的订单不允许退号
|
||||
throw new BusinessException("退号失败:订单已发药,不能退号");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 3. 门诊诊前退号业务(Bug #506)——同步更新多表状态
|
||||
// -----------------------------------------------------------------
|
||||
// 3.1 更新 order_main
|
||||
OrderMain updateMain = new OrderMain();
|
||||
updateMain.setId(orderId);
|
||||
updateMain.setStatus(OrderStatus.CANCELLED.getCode()); // 0 已取消
|
||||
updateMain.setPayStatus(OrderStatus.REFUNDED.getCode()); // 3 已退费(在 PRD 中对应的枚举值)
|
||||
updateMain.setCancelTime(new Date());
|
||||
updateMain.setCancelReason("诊前退号");
|
||||
orderMainMapper.updateByPrimaryKeySelective(updateMain);
|
||||
|
||||
// 3.2 更新对应的号源 slot
|
||||
// order_main 中保存的 slotId(这里假设字段名为 scheduleSlotId)
|
||||
Long slotId = orderMain.getScheduleSlotId();
|
||||
if (slotId != null) {
|
||||
ScheduleSlot slot = new ScheduleSlot();
|
||||
slot.setId(slotId);
|
||||
slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); // 0 待约
|
||||
slot.setOrderId(null); // 解除关联
|
||||
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
||||
}
|
||||
|
||||
// 3.3 更新号源池 pool(已预约数-1,版本号+1)
|
||||
Long poolId = orderMain.getSchedulePoolId();
|
||||
if (poolId != null) {
|
||||
SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(poolId);
|
||||
if (pool != null) {
|
||||
SchedulePool updatePool = new SchedulePool();
|
||||
updatePool.setId(poolId);
|
||||
updatePool.setVersion(pool.getVersion() + 1);
|
||||
updatePool.setBookedNum(pool.getBookedNum() - 1);
|
||||
schedulePoolMapper.updateByPrimaryKeySelective(updatePool);
|
||||
// 2. 仅对药品类医嘱执行已发药校验,检验/检查类医嘱直接跳过
|
||||
if (!isLabOrder(orderMain)) {
|
||||
// 药品类医嘱:检查是否已发药
|
||||
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
|
||||
boolean hasDispensed = details != null && details.stream()
|
||||
.anyMatch(d -> OrderStatus.DISPENSED.getCode().equals(d.getDispenseStatus()));
|
||||
if (hasDispensed) {
|
||||
throw new BusinessException("该医嘱已发药,不能撤回");
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 4. 生成退费日志(保持原有逻辑不变)
|
||||
// -----------------------------------------------------------------
|
||||
RefundLog refundLog = new RefundLog();
|
||||
refundLog.setOrderId(orderId);
|
||||
refundLog.setRefundAmount(orderMain.getPayAmount());
|
||||
refundLog.setRefundTime(new Date());
|
||||
refundLog.setRefundReason("诊前退号");
|
||||
refundLogMapper.insert(refundLog);
|
||||
// 3. 执行撤回业务:更新主表状态、记录撤回日志、回滚关联资源
|
||||
orderMain.setStatus(OrderStatus.CANCELED.getCode());
|
||||
orderMain.setCancelTime(new Date());
|
||||
orderMain.setCancelReason("撤回医嘱");
|
||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 5. 业务结束
|
||||
// -----------------------------------------------------------------
|
||||
log.info("订单[{}] 诊前退号成功,已同步更新 order_main、schedule_slot、schedule_pool 状态", orderId);
|
||||
return true;
|
||||
// 记录撤回日志
|
||||
RefundLog logEntry = new RefundLog();
|
||||
logEntry.setOrderId(orderId);
|
||||
logEntry.setOperateTime(new Date());
|
||||
logEntry.setOperateUser("系统"); // 实际项目中应使用当前登录用户
|
||||
logEntry.setRemark("医嘱撤回");
|
||||
refundLogMapper.insert(logEntry);
|
||||
|
||||
// 如有预约号源,需要回滚 slot 与 pool 状态(仅在存在时执行)
|
||||
if (orderMain.getScheduleSlotId() != null) {
|
||||
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(orderMain.getScheduleSlotId());
|
||||
if (slot != null) {
|
||||
slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode());
|
||||
slot.setOrderId(null);
|
||||
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
||||
}
|
||||
|
||||
SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(orderMain.getSchedulePoolId());
|
||||
if (pool != null) {
|
||||
pool.setVersion(pool.getVersion() + 1);
|
||||
pool.setBookedNum(pool.getBookedNum() - 1);
|
||||
schedulePoolMapper.updateByPrimaryKeySelective(pool);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("医嘱撤回成功,orderId={}", orderId);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 其余业务方法保持不变
|
||||
// -----------------------------------------------------------------
|
||||
// ... 省略其余实现 ...
|
||||
/**
|
||||
* 判断当前医嘱是否为检验/检查类(实验室)医嘱。
|
||||
*
|
||||
* <p>实现依据:
|
||||
* <ul>
|
||||
* <li>order_main.type 字段为 {@code "LAB"}、{@code "EXAM"} 等标识检验/检查的值。</li>
|
||||
* <li>若 type 字段为空或无法确定,则进一步检查 order_detail.item_type
|
||||
* 是否属于实验室类别(通过约定的字典值 {@code "LAB"})。</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param orderMain 主医嘱对象,非空
|
||||
* @return true 表示为检验/检查类医嘱,false 表示为药品类医嘱
|
||||
*/
|
||||
private boolean isLabOrder(OrderMain orderMain) {
|
||||
// 直接使用主表的 type 字段判断(业务约定)
|
||||
String type = orderMain.getType();
|
||||
if (type != null) {
|
||||
String normalized = type.trim().toUpperCase();
|
||||
if (Arrays.asList("LAB", "EXAM", "CHECK", "INSPECTION").contains(normalized)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容旧数据:检查明细的 item_type 是否为实验室
|
||||
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderMain.getId());
|
||||
if (details != null) {
|
||||
return details.stream()
|
||||
.anyMatch(d -> {
|
||||
String itemType = d.getItemType();
|
||||
return itemType != null && "LAB".equalsIgnoreCase(itemType.trim());
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 其余业务方法保持不变...
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user