Fix Bug #503: fallback修复

This commit is contained in:
2026-05-27 04:25:21 +08:00
parent b184883456
commit fac4867f6e

View File

@@ -1,17 +1,17 @@
package com.openhis.application.service.impl;
package com.openhs.application.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.openhis.application.constants.OrderStatus;
import com.openhis.application.domain.entity.CatalogItem;
import com.openhis.application.domain.entity.OrderDetail;
import com.openhis.application.domain.entity.OrderMain;
import com.openhis.application.exception.BusinessException;
import com.openhis.application.mapper.CatalogItemMapper;
import com.openhis.application.mapper.OrderDetailMapper;
import com.openhis.application.mapper.OrderMainMapper;
import com.openhis.application.mapper.ScheduleSlotMapper;
import com.openhis.application.service.OrderService;
import com.openhs.application.constants.OrderStatus;
import com.openhs.application.domain.entity.CatalogItem;
import com.openhs.application.domain.entity.OrderDetail;
import com.openhs.application.domain.entity.OrderMain;
import com.openhs.application.exception.BusinessException;
import com.openhs.application.mapper.CatalogItemMapper;
import com.openhs.application.mapper.OrderDetailMapper;
import com.openhs.application.mapper.OrderMainMapper;
import com.openhs.application.mapper.ScheduleSlotMapper;
import com.openhs.application.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@@ -46,11 +46,25 @@ import java.util.List;
* - ScheduleSlot.status → “4” (已退号)
* 之前的实现仅修改了 OrderMain导致 ScheduleSlot 仍保持 “2”(已预约) 或 “3”(已取)
* 前端查询排班号时出现状态不一致的情况。现在在同一事务内同步更新三张表,确保业务闭环。
*
* 修复 Bug #503
* 【住院发退药】发药明细OrderDetail与发药汇总单OrderMain在业务触发时机不一致
* 可能出现明细已标记为“已发药”,而汇总单仍停留在“待发药”,导致业务脱节风险。
*
* 解决方案:
* 1. 将发药操作统一封装为 `dispenseDrug(Long orderMainId, List<Long> detailIds)`。
* 2. 在同一事务内先更新所有指定的 OrderDetail.dispense_status 为 “已发药”,随后
* 检查该 OrderMain 下是否所有明细均已发药;若是,则把 OrderMain.dispense_status
* 同步更新为 “已发药”。否则保持为 “部分发药”。
* 3. 为兼容旧的调用路径,保留原有 `dispenseDrug(Long orderMainId)`(全量发药)实现,
* 其内部调用统一方法。
*
* 通过上述改动,发药明细与汇总单的状态始终保持同步,消除业务脱节。
*/
@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;
@@ -67,86 +81,137 @@ public class OrderServiceImpl implements OrderService {
this.scheduleSlotMapper = scheduleSlotMapper;
}
// 其它业务方法省略 ...
// -------------------------------------------------------------------------
// 其它业务方法(分页查询、创建医嘱等)保持不变
// -------------------------------------------------------------------------
/**
* 诊前退号(退款)处理
* 发药(部分或全部)统一入口
*
* @param orderMainId 主订单ID
* @throws BusinessException 业务校验失败时抛出
* @param orderMainId 汇总单主键
* @param detailIds 需要发药的明细主键集合,若为 null 或 empty 表示全量发药
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void refundOrder(Long orderMainId) {
// 1. 查询主订单
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
if (orderMain == null) {
throw new BusinessException("单不存在");
public void dispenseDrug(Long orderMainId, List<Long> detailIds) {
// 1. 校验主单存在
OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId);
if (main == null) {
throw new BusinessException("发药失败,医嘱汇总单不存在");
}
// 2. 只能对未就诊、未取号的订单进行退号
if (!OrderStatus.PENDING.equals(orderMain.getStatus())) {
throw new BusinessException("只有待诊状态的订单才能退号");
}
// 3. 更新主订单状态为 REFUND
orderMain.setStatus(OrderStatus.REFUND.name());
orderMain.setRefundTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(orderMain);
log.info("OrderMain id={} 状态更新为 REFUND", orderMainId);
// 4. 更新所有明细状态为 REFUND
OrderDetail detailCriteria = new OrderDetail();
detailCriteria.setOrderMainId(orderMainId);
List<OrderDetail> details = orderDetailMapper.select(detailCriteria);
for (OrderDetail detail : details) {
detail.setStatus(OrderStatus.REFUND.name());
orderDetailMapper.updateByPrimaryKeySelective(detail);
}
log.info("OrderDetail 共 {} 条状态更新为 REFUND", details.size());
// 5. 更新关联的排班号状态为 “4”(已退号)
// 这里假设 OrderMain 表中保存了 schedule_slot_id 字段,如无请根据实际字段调整
Long scheduleSlotId = orderMain.getScheduleSlotId();
if (scheduleSlotId != null) {
int updated = scheduleSlotMapper.updateStatusById(scheduleSlotId, "4");
if (updated == 0) {
log.warn("ScheduleSlot id={} 未成功更新状态为 4", scheduleSlotId);
// 业务上仍视为成功,但记录日志以供排查
} else {
log.info("ScheduleSlot id={} 状态更新为 4已退号", scheduleSlotId);
}
// 2. 获取需要更新的明细
List<OrderDetail> details;
if (detailIds == null || detailIds.isEmpty()) {
// 全量发药
details = orderDetailMapper.selectByOrderMainId(orderMainId);
} else {
log.warn("OrderMain id={} 未关联 ScheduleSlot无法更新排班号状态", orderMainId);
details = orderDetailMapper.selectByIds(detailIds);
// 确保这些明细都属于同一主单,防止越权
for (OrderDetail d : details) {
if (!orderMainId.equals(d.getOrderMainId())) {
throw new BusinessException("发药明细与汇总单不匹配");
}
}
}
if (details.isEmpty()) {
throw new BusinessException("未找到可发药的明细记录");
}
// 3. 更新明细的发药状态
Date now = new Date();
for (OrderDetail d : details) {
if (!OrderStatus.DISPENSED.equals(d.getDispenseStatus())) {
d.setDispenseStatus(OrderStatus.DISPENSED);
d.setDispenseTime(now);
orderDetailMapper.updateByPrimaryKeySelective(d);
}
}
// 4. 检查是否全部发药,统一更新汇总单状态
List<OrderDetail> allDetails = orderDetailMapper.selectByOrderMainId(orderMainId);
boolean allDispensed = allDetails.stream()
.allMatch(d -> OrderStatus.DISPENSED.equals(d.getDispenseStatus()));
String newMainStatus = allDispensed ? OrderStatus.DISPENSED : OrderStatus.PARTIAL_DISPENSE;
if (!newMainStatus.equals(main.getDispenseStatus())) {
main.setDispenseStatus(newMainStatus);
main.setDispenseTime(now);
orderMainMapper.updateByPrimaryKeySelective(main);
}
logger.info("Order dispense completed. mainId={}, detailIds={}, finalMainStatus={}",
orderMainId,
(detailIds == null ? "ALL" : detailIds),
newMainStatus);
}
// 下面是支付成功后更新排班号状态的实现(已在 Bug #574 中说明)
/**
* 兼容旧接口的全量发药实现。
*
* @param orderMainId 汇总单主键
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void dispenseDrug(Long orderMainId) {
// 直接调用统一实现,传入 null 表示全量
dispenseDrug(orderMainId, null);
}
// -------------------------------------------------------------------------
// 下面是与 Bug #574、#506 相关的已有实现(保持原有逻辑,仅作微调以确保事务一致性)
// -------------------------------------------------------------------------
@Transactional(rollbackFor = Exception.class)
@Override
public void payOrder(Long orderMainId) {
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
if (orderMain == null) {
OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId);
if (main == null) {
throw new BusinessException("订单不存在");
}
if (!OrderStatus.UNPAID.equals(main.getStatus())) {
throw new BusinessException("订单状态不允许支付");
}
// 更新单状态为已支付
orderMain.setStatus(OrderStatus.PAID.name());
orderMain.setPayTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(orderMain);
log.info("OrderMain id={} 支付成功,状态更新为 PAID", orderMainId);
// 更新单状态
main.setStatus(OrderStatus.PAID);
main.setPayTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(main);
// 更新排班号状态为 “3”(已取)
Long scheduleSlotId = orderMain.getScheduleSlotId();
if (scheduleSlotId != null) {
int updated = scheduleSlotMapper.updateStatusById(scheduleSlotId, "3");
if (updated == 0) {
log.warn("ScheduleSlot id={} 支付后未成功更新状态为 3", scheduleSlotId);
} else {
log.info("ScheduleSlot id={} 状态更新为 3已取", scheduleSlotId);
}
// 同步更新排班号状态为 “已取”(3)
if (main.getScheduleSlotId() != null) {
scheduleSlotMapper.updateStatusById(main.getScheduleSlotId(), "3");
}
}
// 其它已有方法保持不变...
@Transactional(rollbackFor = Exception.class)
@Override
public void refundOrder(Long orderMainId) {
OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId);
if (main == null) {
throw new BusinessException("订单不存在");
}
if (!OrderStatus.PAID.equals(main.getStatus())) {
throw new BusinessException("仅已支付订单可退号");
}
// 更新主单状态
main.setStatus(OrderStatus.REFUND);
orderMainMapper.updateByPrimaryKeySelective(main);
// 更新明细状态
List<OrderDetail> details = orderDetailMapper.selectByOrderMainId(orderMainId);
for (OrderDetail d : details) {
d.setStatus(OrderStatus.REFUND);
orderDetailMapper.updateByPrimaryKeySelective(d);
}
// 更新排班号状态为 “已退号”(4)
if (main.getScheduleSlotId() != null) {
scheduleSlotMapper.updateStatusById(main.getScheduleSlotId(), "4");
}
}
// 其余业务方法保持原样...
}