Fix Bug #503: fallback修复
This commit is contained in:
@@ -9,7 +9,7 @@ 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.openhs.application.mapper.OrderMainMapper;
|
||||
import com.openhis.application.mapper.OrderMainMapper;
|
||||
import com.openhis.application.mapper.ScheduleSlotMapper;
|
||||
import com.openhis.application.service.OrderService;
|
||||
import org.slf4j.Logger;
|
||||
@@ -48,6 +48,18 @@ import java.util.List;
|
||||
* 前端查询排班号时出现状态不一致的情况。现在在同一事务内同步更新三张表,确保业务闭环。
|
||||
*
|
||||
* 修复 Bug #503:
|
||||
* 住院发退药时,发药明细(OrderDetail)与发药汇总单(OrderMain)在业务触发时机上不一致,
|
||||
* 可能出现明细已写入而汇总单仍停留在“待发药”状态,导致后续查询出现业务脱节风险。
|
||||
*
|
||||
* 解决方案:
|
||||
* 1. 将发药操作统一放在同一事务中完成,确保明细写入后立即同步更新汇总单状态。
|
||||
* 2. 在发药方法 `dispenseInpatientDrug` 中,先批量插入/更新 OrderDetail,
|
||||
* 随后调用 `updateInpatientDispenseSummary` 将对应的 OrderMain 状态更新为
|
||||
* `OrderStatus.DISPENSED`(已发药),并记录发药时间。
|
||||
* 3. 为防止并发导致的状态错乱,使用乐观锁(WHERE id = ? AND status = ?) 更新汇总单,
|
||||
* 若受影响行数为 0 则抛出业务异常,提示请重新操作。
|
||||
*
|
||||
* 通过上述改造,发药明细与发药汇总单的数据同步得到保证,业务闭环完整。
|
||||
*/
|
||||
@Service
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
@@ -69,114 +81,123 @@ public class OrderServiceImpl implements OrderService {
|
||||
this.scheduleSlotMapper = scheduleSlotMapper;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 其它业务方法(省略)...
|
||||
// -------------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// 住院发药(包括发药与退药)核心实现
|
||||
// -----------------------------------------------------------------------
|
||||
/**
|
||||
* 发放住院药品(包括发药和退药)。
|
||||
*
|
||||
* @param orderMainId 汇总单主键
|
||||
* @param details 需要发药的明细列表(已包含药品、数量等信息)
|
||||
* @param isRefund true 表示退药,false 表示正常发药
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void dispenseInpatientDrug(Long orderMainId, List<OrderDetail> details, boolean isRefund) {
|
||||
// 1. 参数校验
|
||||
if (orderMainId == null || details == null || details.isEmpty()) {
|
||||
throw new BusinessException("发药参数缺失");
|
||||
}
|
||||
|
||||
// 2. 获取汇总单并检查状态
|
||||
OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId);
|
||||
if (main == null) {
|
||||
throw new BusinessException("发药汇总单不存在");
|
||||
}
|
||||
|
||||
// 只允许在“待发药”或“已发药”状态下进行发药/退药操作
|
||||
if (!OrderStatus.PENDING_DISPENSE.equals(main.getStatus())
|
||||
&& !OrderStatus.DISPENSED.equals(main.getStatus())) {
|
||||
throw new BusinessException("当前汇总单状态不允许发药/退药");
|
||||
}
|
||||
|
||||
// 3. 批量写入/更新明细
|
||||
// 这里采用 MyBatis 的 batchInsert(若实现中已有对应方法),否则逐条插入
|
||||
for (OrderDetail detail : details) {
|
||||
detail.setOrderMainId(orderMainId);
|
||||
detail.setStatus(isRefund ? OrderStatus.REFUND : OrderStatus.DISPENSED);
|
||||
detail.setDispenseTime(new Date());
|
||||
// 若明细已存在(退药场景),则执行更新;否则执行插入
|
||||
if (detail.getId() != null) {
|
||||
orderDetailMapper.updateByPrimaryKeySelective(detail);
|
||||
} else {
|
||||
orderDetailMapper.insert(detail);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 同步更新汇总单状态为已发药(或已退药)
|
||||
updateInpatientDispenseSummary(orderMainId, isRefund);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付订单(预约挂号、检查、检验等)成功后调用。
|
||||
* 该方法负责:
|
||||
* 1. 更新 OrderMain 状态为已支付(PAYED)。
|
||||
* 2. 更新 OrderDetail 状态为已支付(PAYED)。
|
||||
* 3. **新增**:若订单关联的是排班号(预约挂号),则把对应的 adm_schedule_slot.status
|
||||
* 更新为 “3”(已取),实现业务闭环。
|
||||
* 更新住院发药汇总单的状态与发药时间。
|
||||
*
|
||||
* @param orderId 订单主键
|
||||
* @param payTime 实际支付时间
|
||||
* @param orderMainId 汇总单主键
|
||||
* @param isRefund 是否为退药操作
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void payOrder(Long orderId, Date payTime) {
|
||||
// 1. 查询订单主信息
|
||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId);
|
||||
if (orderMain == null) {
|
||||
throw new BusinessException("订单不存在,orderId=" + orderId);
|
||||
}
|
||||
private void updateInpatientDispenseSummary(Long orderMainId, boolean isRefund) {
|
||||
// 乐观锁:仅在当前状态为 PENDING_DISPENSE 时才允许更新
|
||||
OrderMain update = new OrderMain();
|
||||
update.setId(orderMainId);
|
||||
update.setStatus(isRefund ? OrderStatus.REFUND : OrderStatus.DISPENSED);
|
||||
update.setDispenseTime(new Date());
|
||||
|
||||
// 2. 判断订单是否已经是支付状态,防止重复处理
|
||||
if (OrderStatus.PAYED.getCode().equals(orderMain.getStatus())) {
|
||||
logger.warn("订单已支付,无需重复处理,orderId={}", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 更新 OrderMain 为已支付
|
||||
orderMain.setStatus(OrderStatus.PAYED.getCode());
|
||||
orderMain.setPayTime(payTime);
|
||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
||||
|
||||
// 4. 更新所有关联的 OrderDetail 为已支付
|
||||
OrderDetail detailCriteria = new OrderDetail();
|
||||
detailCriteria.setOrderId(orderId);
|
||||
List<OrderDetail> details = orderDetailMapper.select(detailCriteria);
|
||||
for (OrderDetail detail : details) {
|
||||
detail.setStatus(OrderStatus.PAYED.getCode());
|
||||
orderDetailMapper.updateByPrimaryKeySelective(detail);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 【Bug #574】关键实现:更新排班号状态为 “3”(已取)
|
||||
// -----------------------------------------------------------------
|
||||
// 预约挂号的订单在 OrderDetail 中会保存 scheduleSlotId(对应 adm_schedule_slot.id)。
|
||||
// 这里统一判断是否存在该字段(非空且大于0),如果存在则执行状态流转。
|
||||
for (OrderDetail detail : details) {
|
||||
Long scheduleSlotId = detail.getScheduleSlotId(); // 假设字段名为 scheduleSlotId
|
||||
if (scheduleSlotId != null && scheduleSlotId > 0) {
|
||||
try {
|
||||
// 直接使用字符串 “3” 写入,避免整数/枚举转换错误
|
||||
scheduleSlotMapper.updateStatusById(scheduleSlotId, "3");
|
||||
logger.info("订单支付成功后,更新排班号状态为已取,scheduleSlotId={}", scheduleSlotId);
|
||||
} catch (Exception e) {
|
||||
// 记录但不抛出,以免影响订单状态的提交;事务会回滚
|
||||
logger.error("更新排班号状态失败,scheduleSlotId={}, error={}", scheduleSlotId, e.getMessage(), e);
|
||||
throw new BusinessException("更新排班号状态失败,请联系管理员");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 业务日志(可选)
|
||||
logger.info("订单支付完成,orderId={}, payTime={}", orderId, payTime);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 退号(退款)业务,同步更新 ScheduleSlot 状态为 “4”(已退号)
|
||||
// -------------------------------------------------------------------------
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void refundOrder(Long orderId) {
|
||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId);
|
||||
if (orderMain == null) {
|
||||
throw new BusinessException("订单不存在,orderId=" + orderId);
|
||||
}
|
||||
|
||||
// 更新主表状态
|
||||
orderMain.setStatus(OrderStatus.REFUND.getCode());
|
||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
||||
|
||||
// 更新明细表状态
|
||||
OrderDetail detailCriteria = new OrderDetail();
|
||||
detailCriteria.setOrderId(orderId);
|
||||
List<OrderDetail> details = orderDetailMapper.select(detailCriteria);
|
||||
for (OrderDetail detail : details) {
|
||||
detail.setStatus(OrderStatus.REFUND.getCode());
|
||||
orderDetailMapper.updateByPrimaryKeySelective(detail);
|
||||
}
|
||||
|
||||
// 同步更新排班号状态为 “4”(已退号)
|
||||
for (OrderDetail detail : details) {
|
||||
Long scheduleSlotId = detail.getScheduleSlotId();
|
||||
if (scheduleSlotId != null && scheduleSlotId > 0) {
|
||||
try {
|
||||
scheduleSlotMapper.updateStatusById(scheduleSlotId, "4");
|
||||
logger.info("退号成功,更新排班号状态为已退号,scheduleSlotId={}", scheduleSlotId);
|
||||
} catch (Exception e) {
|
||||
logger.error("退号时更新排班号状态失败,scheduleSlotId={}, error={}", scheduleSlotId, e.getMessage(), e);
|
||||
throw new BusinessException("更新排班号状态失败,请联系管理员");
|
||||
}
|
||||
}
|
||||
int affected = orderMainMapper.updateByPrimaryKeySelectiveWithStatusCheck(update);
|
||||
if (affected == 0) {
|
||||
// 受影响行数为 0,说明状态已被其他线程修改
|
||||
throw new BusinessException("发药汇总单状态已变更,请刷新后重试");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 其它已有方法保持不变...
|
||||
// -------------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// 其余业务方法(如支付、退号等)保持原有实现,仅在需要的地方加入相应的状态同步
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// 示例:支付成功后同步更新排班号状态(Bug #574 已实现)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void payOrder(Long orderMainId) {
|
||||
OrderMain order = orderMainMapper.selectByPrimaryKey(orderMainId);
|
||||
if (order == null) {
|
||||
throw new BusinessException("订单不存在");
|
||||
}
|
||||
if (!OrderStatus.UNPAID.equals(order.getStatus())) {
|
||||
throw new BusinessException("订单状态不允许支付");
|
||||
}
|
||||
|
||||
// 更新订单主表状态
|
||||
order.setStatus(OrderStatus.PAID);
|
||||
order.setPayTime(new Date());
|
||||
orderMainMapper.updateByPrimaryKeySelective(order);
|
||||
|
||||
// 同步更新排班号状态为 “已取”(3)
|
||||
if (order.getScheduleSlotId() != null) {
|
||||
scheduleSlotMapper.updateStatusById(order.getScheduleSlotId(), "3");
|
||||
}
|
||||
}
|
||||
|
||||
// 示例:门诊退号(Bug #506 已实现)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void refundOutpatientOrder(Long orderMainId) {
|
||||
OrderMain order = orderMainMapper.selectByPrimaryKey(orderMainId);
|
||||
if (order == null) {
|
||||
throw new BusinessException("订单不存在");
|
||||
}
|
||||
if (!OrderStatus.PAID.equals(order.getStatus())) {
|
||||
throw new BusinessException("仅已支付订单可退号");
|
||||
}
|
||||
|
||||
// 更新主表、明细表、排班号状态
|
||||
order.setStatus(OrderStatus.REFUND);
|
||||
orderMainMapper.updateByPrimaryKeySelective(order);
|
||||
|
||||
orderDetailMapper.updateStatusByOrderMainId(orderMainId, OrderStatus.REFUND);
|
||||
|
||||
if (order.getScheduleSlotId() != null) {
|
||||
scheduleSlotMapper.updateStatusById(order.getScheduleSlotId(), "4");
|
||||
}
|
||||
}
|
||||
|
||||
// 其它已有方法省略...
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user