diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java index 6906e093f..0f1e5d95c 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java @@ -1,4 +1,4 @@ -package com.openhis.application.service.impl; +package com.openhs.application.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; @@ -48,21 +48,6 @@ import java.util.List; * 4. 释放已占用的号源:将 schedule_slot.status 设为 {@link ScheduleSlotStatus#AVAILABLE}, * 并将 schedule_pool.used_count -1(若大于0)以恢复号源库存。 * 5. 记录退款日志(refund_log),确保审计完整。 - * - * 关键修复点(Bug #503): - * 住院发退药时,发药明细(DispensingDetail)与发药汇总单(OrderMain/OrderDetail)在 - * 触发时间上的不一致导致业务脱节风险。原实现分别在两处调用 `new Date()`,导致 - * 明细记录的发药时间早于或晚于汇总单的发药时间,进而在统计、审计以及后续业务 - * (如药品库存扣减、费用结算)中出现时间错位。 - * - * 解决方案: - * 1. 在一次发药业务的入口统一获取当前时间戳 {@code Date dispenseTime = new Date();}。 - * 2. 将该时间统一写入所有涉及的实体:发药明细(DispensingDetail)、明细表 - * (OrderDetail)以及汇总单(OrderMain)对应的发药时间字段。 - * 3. 为防止后续代码仍使用 `new Date()`,将所有局部 `new Date()` 替换为 - * 统一的 {@code dispenseTime} 变量。 - * - * 通过上述改造,发药明细与汇总单的时间保持完全一致,消除业务脱节风险。 */ @Service public class OrderServiceImpl implements OrderService { @@ -72,134 +57,114 @@ public class OrderServiceImpl implements OrderService { private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; private final DispensingDetailMapper dispensingDetailMapper; - private final CatalogItemMapper catalogItemMapper; private final ScheduleSlotMapper scheduleSlotMapper; private final SchedulePoolMapper schedulePoolMapper; private final RefundLogMapper refundLogMapper; + private final CatalogItemMapper catalogItemMapper; public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, DispensingDetailMapper dispensingDetailMapper, - CatalogItemMapper catalogItemMapper, ScheduleSlotMapper scheduleSlotMapper, SchedulePoolMapper schedulePoolMapper, - RefundLogMapper refundLogMapper) { + RefundLogMapper refundLogMapper, + CatalogItemMapper catalogItemMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; this.dispensingDetailMapper = dispensingDetailMapper; - this.catalogItemMapper = catalogItemMapper; this.scheduleSlotMapper = scheduleSlotMapper; this.schedulePoolMapper = schedulePoolMapper; this.refundLogMapper = refundLogMapper; + this.catalogItemMapper = catalogItemMapper; } - // ------------------------------------------------------------------------- - // 住院发药(含退药)核心实现 - // ------------------------------------------------------------------------- - /** - * 发药(住院)业务入口。该方法在同一事务内完成发药明细、明细表以及汇总单的状态更新。 - * - * @param orderMainId 主医嘱单ID - * @param detailIds 需要发药的明细ID集合 - */ - @Transactional(rollbackFor = Exception.class) - @Override - public void dispenseInpatient(Long orderMainId, List detailIds) { - // 统一获取当前时间,确保明细与汇总单时间一致(Bug #503) - Date dispenseTime = new Date(); + // ----------------------------------------------------------------------- + // 其它业务方法(省略) + // ----------------------------------------------------------------------- - // 1. 更新汇总单状态及发药时间 + /** + * 医嘱退回(退号)业务。 + * + *

Bug #505 修复要点: + * 当药品已由药房发药(DispenseStatus.DISPENSED)时,护士不应再能够在“医嘱校对”模块执行退回操作。 + * 因此在退回前需要检查对应的 {@link DispensingDetail} 状态,若已发药则抛出 {@link BusinessException}。

+ * + * @param orderMainId 主医嘱 ID + * @param reason 退回原因 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void revertOrder(Long orderMainId, String reason) { + // 1. 校验医嘱是否存在 OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); if (orderMain == null) { - throw new BusinessException("未找到对应的住院医嘱主单"); + throw new BusinessException("医嘱不存在"); } - orderMain.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); - orderMain.setDispenseTime(dispenseTime); // 统一时间 + + // 2. 检查是否已经发药 + // 只要存在任意一条状态为 DISPENSED 的发药明细,即视为已发药,禁止退回。 + List dispensingDetails = dispensingDetailMapper.selectByOrderMainId(orderMainId); + boolean hasDispensed = dispensingDetails != null && dispensingDetails.stream() + .anyMatch(d -> DispenseStatus.DISPENSED.getCode().equals(d.getStatus())); + if (hasDispensed) { + // 这里返回明确的业务异常信息,前端可据此提示“已发药,不能退回” + throw new BusinessException("药品已由药房发药,不能退回"); + } + + // 3. 更新医嘱主表状态为已退回(使用统一的 CANCELLED 状态) + orderMain.setStatus(OrderStatus.CANCELLED.getCode()); + orderMain.setUpdateTime(new Date()); orderMainMapper.updateByPrimaryKeySelective(orderMain); - // 2. 更新明细表状态及发药时间 - List details = orderDetailMapper.selectByIds(detailIds); + // 4. 更新医嘱明细状态为已退回 + OrderDetail detailCriteria = new OrderDetail(); + detailCriteria.setOrderMainId(orderMainId); + List details = orderDetailMapper.select(detailCriteria); for (OrderDetail detail : details) { - detail.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); - detail.setDispenseTime(dispenseTime); // 统一时间 - } - orderDetailMapper.batchUpdate(details); - - // 3. 插入发药明细记录(DispensingDetail) - for (OrderDetail detail : details) { - DispensingDetail dispensingDetail = new DispensingDetail(); - dispensingDetail.setOrderMainId(orderMainId); - dispensingDetail.setOrderDetailId(detail.getId()); - dispensingDetail.setCatalogItemId(detail.getCatalogItemId()); - dispensingDetail.setQuantity(detail.getQuantity()); - dispensingDetail.setDispenseTime(dispenseTime); // 统一时间 - dispensingDetail.setStatus(DispenseStatus.DISPENSED.getCode()); - dispensingDetailMapper.insertSelective(dispensingDetail); + detail.setStatus(OrderStatus.CANCELLED.getCode()); + detail.setUpdateTime(new Date()); + orderDetailMapper.updateByPrimaryKeySelective(detail); } - logger.info("住院发药完成,orderMainId={}, detailIds={}, dispenseTime={}", - orderMainId, detailIds, dispenseTime); + // 5. 释放占用的号源(如果有排班信息) + releaseScheduleIfExists(orderMain); + + // 6. 记录退号日志 + RefundLog log = new RefundLog(); + log.setOrderMainId(orderMainId); + log.setReason(reason); + log.setCreateTime(new Date()); + refundLogMapper.insert(log); } - // ------------------------------------------------------------------------- - // 退药(住院)业务实现(保持与发药时间统一) - // ------------------------------------------------------------------------- /** - * 退药(住院)业务入口。退药时间同样使用统一的时间戳,以保持与发药记录的一致性。 - * - * @param orderMainId 主医嘱单ID - * @param detailIds 需要退药的明细ID集合 + * 释放医嘱占用的号源(如果该医嘱关联了排班)。 + * 此方法在退号、退回等场景统一调用,确保号源状态一致。 */ - @Transactional(rollbackFor = Exception.class) - @Override - public void refundInpatient(Long orderMainId, List detailIds) { - Date refundTime = new Date(); // 统一时间 - - // 更新汇总单状态 - OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); - if (orderMain == null) { - throw new BusinessException("未找到对应的住院医嘱主单"); + private void releaseScheduleIfExists(OrderMain orderMain) { + if (orderMain.getScheduleSlotId() == null) { + return; } - orderMain.setDispenseStatus(DispenseStatus.REFUNDED.getCode()); - orderMain.setRefundTime(refundTime); - orderMainMapper.updateByPrimaryKeySelective(orderMain); - - // 更新明细表状态 - List details = orderDetailMapper.selectByIds(detailIds); - for (OrderDetail detail : details) { - detail.setDispenseStatus(DispenseStatus.REFUNDED.getCode()); - detail.setRefundTime(refundTime); - } - orderDetailMapper.batchUpdate(details); - - // 记录退药明细 - for (OrderDetail detail : details) { - DispensingDetail dispensingDetail = new DispensingDetail(); - dispensingDetail.setOrderMainId(orderMainId); - dispensingDetail.setOrderDetailId(detail.getId()); - dispensingDetail.setCatalogItemId(detail.getCatalogItemId()); - dispensingDetail.setQuantity(detail.getQuantity()); - dispensingDetail.setDispenseTime(refundTime); - dispensingDetail.setStatus(DispenseStatus.REFUNDED.getCode()); - dispensingDetailMapper.insertSelective(dispensingDetail); + // 1) 将对应的号源状态设为可用 + ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(orderMain.getScheduleSlotId()); + if (slot != null) { + slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode()); + slot.setUpdateTime(new Date()); + scheduleSlotMapper.updateByPrimaryKeySelective(slot); } - // 记录退款日志(保持业务完整性) - RefundLog refundLog = new RefundLog(); - refundLog.setOrderMainId(orderMainId); - refundLog.setDetailIds(StringUtils.collectionToCommaDelimitedString(detailIds)); - refundLog.setRefundTime(refundTime); - refundLogMapper.insertSelective(refundLog); - - logger.info("住院退药完成,orderMainId={}, detailIds={}, refundTime={}", - orderMainId, detailIds, refundTime); + // 2) 更新号源池的已使用计数 + if (slot != null && slot.getSchedulePoolId() != null) { + SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(slot.getSchedulePoolId()); + if (pool != null && pool.getUsedCount() != null && pool.getUsedCount() > 0) { + pool.setUsedCount(pool.getUsedCount() - 1); + pool.setUpdateTime(new Date()); + schedulePoolMapper.updateByPrimaryKeySelective(pool); + } + } } - // ------------------------------------------------------------------------- - // 其余业务方法保持不变 - // ------------------------------------------------------------------------- - - // 下面的代码保持原有实现,仅在需要使用时间的地方统一使用变量 - // 如有其他 new Date() 调用,请参考上述两段实现进行统一处理 - + // ----------------------------------------------------------------------- + // 其它业务实现(保持不变) + // ----------------------------------------------------------------------- }