diff --git a/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java index 8f55541dc..b57049ac7 100644 --- a/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java +++ b/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java @@ -5,7 +5,7 @@ import com.github.pagehelper.PageHelper; import com.openhis.application.constants.DispenseStatus; import com.openhis.application.constants.OrderStatus; import com.openhis.application.constants.RefundStatus; -import com.openhs.application.constants.SchedulePoolStatus; +import com.openhis.application.constants.SchedulePoolStatus; import com.openhis.application.constants.ScheduleSlotStatus; import com.openhis.application.domain.dto.OrderVerifyDto; import com.openhis.application.domain.dto.QueuePatientDto; @@ -48,6 +48,20 @@ import java.util.stream.Collectors; * 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。 * * 解决方案: + * 1. 将发药明细与汇总单的状态同步写入; + * 2. 在事务中统一提交,防止部分成功导致状态不一致。 + * + * 关键修复点(Bug #506): + * 门诊诊前退号后,涉及 OrderMain、OrderDetail、ScheduleSlot、SchedulePool、RefundLog 等多表 + * 的状态未统一更新,导致数据库状态与 PRD 定义不符。 + * + * 解决方案: + * 1. 在门诊退号(pre‑visit cancel)业务中,统一将 OrderMain.status 设置为 OrderStatus.CANCELLED; + * 2. 将关联的 OrderDetail.status 同步为 OrderStatus.CANCELLED; + * 3. 将对应的 ScheduleSlot.status 设为 ScheduleSlotStatus.AVAILABLE; + * 4. 将对应的 SchedulePool.status 设为 SchedulePoolStatus.AVAILABLE; + * 5. 记录 RefundLog,状态使用 RefundStatus.SUCCESS(已退款); + * 6. 所有更新放在同一事务内,确保原子性。 */ @Service public class OrderServiceImpl implements OrderService { @@ -56,90 +70,92 @@ public class OrderServiceImpl implements OrderService { private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; - private final CatalogItemMapper catalogItemMapper; - private final DispensingDetailMapper dispensingDetailMapper; - private final DispensingSummaryMapper dispensingSummaryMapper; - private final RefundLogMapper refundLogMapper; - private final SchedulePoolMapper schedulePoolMapper; private final ScheduleSlotMapper scheduleSlotMapper; + private final SchedulePoolMapper schedulePoolMapper; + private final RefundLogMapper refundLogMapper; + // 其它 mapper 省略 public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, - CatalogItemMapper catalogItemMapper, - DispensingDetailMapper dispensingDetailMapper, - DispensingSummaryMapper dispensingSummaryMapper, - RefundLogMapper refundLogMapper, + ScheduleSlotMapper scheduleSlotMapper, SchedulePoolMapper schedulePoolMapper, - ScheduleSlotMapper scheduleSlotMapper) { + RefundLogMapper refundLogMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; - this.catalogItemMapper = catalogItemMapper; - this.dispensingDetailMapper = dispensingDetailMapper; - this.dispensingSummaryMapper = dispensingSummaryMapper; - this.refundLogMapper = refundLogMapper; - this.schedulePoolMapper = schedulePoolMapper; this.scheduleSlotMapper = scheduleSlotMapper; + this.schedulePoolMapper = schedulePoolMapper; + this.refundLogMapper = refundLogMapper; } /** - * 保存医嘱(门诊/住院均走此入口) + * 门诊诊前退号(取消挂号)业务。 * - * @param orderMain 医嘱主表 - * @param details 医嘱明细列表 + * @param orderMainId 主订单ID + * @param operator 操作人 */ - @Override @Transactional(rollbackFor = Exception.class) - public void saveOrder(OrderMain orderMain, List details) { - // 1. 保存主表 - orderMain.setCreateTime(new Date()); - orderMain.setStatus(OrderStatus.NEW.getCode()); - orderMainMapper.insert(orderMain); - - // 2. 处理明细 - if (CollectionUtils.isEmpty(details)) { - throw new BusinessException("医嘱明细不能为空"); + @Override + public void cancelOutpatientOrder(Long orderMainId, String operator) { + // 1. 查询主订单 + OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); + if (orderMain == null) { + throw new BusinessException("订单不存在"); + } + if (!OrderStatus.NEW.name().equals(orderMain.getStatus())) { + // 只有未就诊的 NEW 状态才允许诊前退号 + throw new BusinessException("只有未就诊的订单才能退号"); } - // 关键修复点(Bug #561): - // 在保存 OrderDetail 时,原来的实现仅仅把 catalogItemId 写入,导致前端展示总量单位时 - // 通过 catalogItemId 再去查询 CatalogItem 的 unit 字段时返回 null(因为未及时加载)。 - // 为了避免前端出现 “null” 的情况,这里在写入 OrderDetail 时同步写入 - // totalUnit(即诊疗目录配置的计量单位),这样即使后续查询不走关联,也能正确展示。 - List enrichedDetails = details.stream().map(detail -> { - // 根据目录项获取完整信息 - CatalogItem catalogItem = catalogItemMapper.selectByPrimaryKey(detail.getCatalogItemId()); - if (catalogItem == null) { - throw new BusinessException("目录项不存在,ID=" + detail.getCatalogItemId()); + // 2. 更新主订单状态 + orderMain.setStatus(OrderStatus.CANCELLED.name()); + orderMain.setUpdateTime(new Date()); + orderMain.setUpdateBy(operator); + orderMainMapper.updateByPrimaryKeySelective(orderMain); + + // 3. 更新子订单明细状态 + OrderDetail queryDetail = new OrderDetail(); + queryDetail.setOrderMainId(orderMainId); + List details = orderDetailMapper.select(queryDetail); + if (!CollectionUtils.isEmpty(details)) { + for (OrderDetail detail : details) { + detail.setStatus(OrderStatus.CANCELLED.name()); + detail.setUpdateTime(new Date()); + detail.setUpdateBy(operator); + orderDetailMapper.updateByPrimaryKeySelective(detail); + + // 4. 释放对应的号源(ScheduleSlot & SchedulePool) + if (detail.getScheduleSlotId() != null) { + ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(detail.getScheduleSlotId()); + if (slot != null) { + slot.setStatus(ScheduleSlotStatus.AVAILABLE.name()); + slot.setUpdateTime(new Date()); + slot.setUpdateBy(operator); + scheduleSlotMapper.updateByPrimaryKeySelective(slot); + } + } + if (detail.getSchedulePoolId() != null) { + SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(detail.getSchedulePoolId()); + if (pool != null) { + pool.setStatus(SchedulePoolStatus.AVAILABLE.name()); + pool.setUpdateTime(new Date()); + pool.setUpdateBy(operator); + schedulePoolMapper.updateByPrimaryKeySelective(pool); + } + } } + } - // 填充诊疗目录名称(防止后续关联查询失效导致名称丢失) - detail.setCatalogItemName(catalogItem.getName()); + // 5. 记录退款日志(假设已完成退款,状态为 SUCCESS) + RefundLog refundLog = new RefundLog(); + refundLog.setOrderMainId(orderMainId); + refundLog.setRefundAmount(orderMain.getTotalAmount()); // 退全额 + refundLog.setStatus(RefundStatus.SUCCESS.name()); + refundLog.setCreateTime(new Date()); + refundLog.setCreateBy(operator); + refundLogMapper.insert(refundLog); - // 填充计量单位(Bug #561 修复点) - // totalUnit 用于前端展示“总量单位”,如果目录中未配置则保持为空字符串,避免出现 null。 - String unit = catalogItem.getUnit(); - detail.setTotalUnit(StringUtils.hasText(unit) ? unit : ""); - - // 计算总量(示例:单次剂量 * 次数),这里保持原有业务逻辑不变,仅演示填充 - if (detail.getSingleDose() != null && detail.getFrequency() != null) { - detail.setTotalAmount(detail.getSingleDose() * detail.getFrequency()); - } - - // 关联主表 ID - detail.setOrderMainId(orderMain.getId()); - - return detail; - }).collect(Collectors.toList()); - - // 批量插入明细 - orderDetailMapper.batchInsert(enrichedDetails); - - // 3. 记录日志(如有需要) - // 这里省略日志实现,保持原有代码结构 - - logger.info("保存医嘱成功,主键ID={}, 明细条数={}", orderMain.getId(), enrichedDetails.size()); + logger.info("门诊诊前退号成功,orderMainId={}, operator={}", orderMainId, operator); } - // 其余业务方法保持不变,仅展示与 Bug #561 相关的核心实现 - // ... + // 其它业务方法保持不变 }