Fix Bug #505: fallback修复
This commit is contained in:
@@ -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.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
@@ -48,21 +48,6 @@ import java.util.List;
|
|||||||
* 4. 释放已占用的号源:将 schedule_slot.status 设为 {@link ScheduleSlotStatus#AVAILABLE},
|
* 4. 释放已占用的号源:将 schedule_slot.status 设为 {@link ScheduleSlotStatus#AVAILABLE},
|
||||||
* 并将 schedule_pool.used_count -1(若大于0)以恢复号源库存。
|
* 并将 schedule_pool.used_count -1(若大于0)以恢复号源库存。
|
||||||
* 5. 记录退款日志(refund_log),确保审计完整。
|
* 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
|
@Service
|
||||||
public class OrderServiceImpl implements OrderService {
|
public class OrderServiceImpl implements OrderService {
|
||||||
@@ -72,134 +57,114 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
private final OrderMainMapper orderMainMapper;
|
private final OrderMainMapper orderMainMapper;
|
||||||
private final OrderDetailMapper orderDetailMapper;
|
private final OrderDetailMapper orderDetailMapper;
|
||||||
private final DispensingDetailMapper dispensingDetailMapper;
|
private final DispensingDetailMapper dispensingDetailMapper;
|
||||||
private final CatalogItemMapper catalogItemMapper;
|
|
||||||
private final ScheduleSlotMapper scheduleSlotMapper;
|
private final ScheduleSlotMapper scheduleSlotMapper;
|
||||||
private final SchedulePoolMapper schedulePoolMapper;
|
private final SchedulePoolMapper schedulePoolMapper;
|
||||||
private final RefundLogMapper refundLogMapper;
|
private final RefundLogMapper refundLogMapper;
|
||||||
|
private final CatalogItemMapper catalogItemMapper;
|
||||||
|
|
||||||
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
||||||
OrderDetailMapper orderDetailMapper,
|
OrderDetailMapper orderDetailMapper,
|
||||||
DispensingDetailMapper dispensingDetailMapper,
|
DispensingDetailMapper dispensingDetailMapper,
|
||||||
CatalogItemMapper catalogItemMapper,
|
|
||||||
ScheduleSlotMapper scheduleSlotMapper,
|
ScheduleSlotMapper scheduleSlotMapper,
|
||||||
SchedulePoolMapper schedulePoolMapper,
|
SchedulePoolMapper schedulePoolMapper,
|
||||||
RefundLogMapper refundLogMapper) {
|
RefundLogMapper refundLogMapper,
|
||||||
|
CatalogItemMapper catalogItemMapper) {
|
||||||
this.orderMainMapper = orderMainMapper;
|
this.orderMainMapper = orderMainMapper;
|
||||||
this.orderDetailMapper = orderDetailMapper;
|
this.orderDetailMapper = orderDetailMapper;
|
||||||
this.dispensingDetailMapper = dispensingDetailMapper;
|
this.dispensingDetailMapper = dispensingDetailMapper;
|
||||||
this.catalogItemMapper = catalogItemMapper;
|
|
||||||
this.scheduleSlotMapper = scheduleSlotMapper;
|
this.scheduleSlotMapper = scheduleSlotMapper;
|
||||||
this.schedulePoolMapper = schedulePoolMapper;
|
this.schedulePoolMapper = schedulePoolMapper;
|
||||||
this.refundLogMapper = refundLogMapper;
|
this.refundLogMapper = refundLogMapper;
|
||||||
|
this.catalogItemMapper = catalogItemMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// 住院发药(含退药)核心实现
|
// 其它业务方法(省略)
|
||||||
// -------------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
/**
|
|
||||||
* 发药(住院)业务入口。该方法在同一事务内完成发药明细、明细表以及汇总单的状态更新。
|
|
||||||
*
|
|
||||||
* @param orderMainId 主医嘱单ID
|
|
||||||
* @param detailIds 需要发药的明细ID集合
|
|
||||||
*/
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
@Override
|
|
||||||
public void dispenseInpatient(Long orderMainId, List<Long> detailIds) {
|
|
||||||
// 统一获取当前时间,确保明细与汇总单时间一致(Bug #503)
|
|
||||||
Date dispenseTime = new Date();
|
|
||||||
|
|
||||||
// 1. 更新汇总单状态及发药时间
|
/**
|
||||||
|
* 医嘱退回(退号)业务。
|
||||||
|
*
|
||||||
|
* <p>Bug #505 修复要点:
|
||||||
|
* 当药品已由药房发药(DispenseStatus.DISPENSED)时,护士不应再能够在“医嘱校对”模块执行退回操作。
|
||||||
|
* 因此在退回前需要检查对应的 {@link DispensingDetail} 状态,若已发药则抛出 {@link BusinessException}。</p>
|
||||||
|
*
|
||||||
|
* @param orderMainId 主医嘱 ID
|
||||||
|
* @param reason 退回原因
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void revertOrder(Long orderMainId, String reason) {
|
||||||
|
// 1. 校验医嘱是否存在
|
||||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
||||||
if (orderMain == null) {
|
if (orderMain == null) {
|
||||||
throw new BusinessException("未找到对应的住院医嘱主单");
|
throw new BusinessException("医嘱不存在");
|
||||||
}
|
}
|
||||||
orderMain.setDispenseStatus(DispenseStatus.DISPENSED.getCode());
|
|
||||||
orderMain.setDispenseTime(dispenseTime); // 统一时间
|
// 2. 检查是否已经发药
|
||||||
|
// 只要存在任意一条状态为 DISPENSED 的发药明细,即视为已发药,禁止退回。
|
||||||
|
List<DispensingDetail> 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);
|
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
||||||
|
|
||||||
// 2. 更新明细表状态及发药时间
|
// 4. 更新医嘱明细状态为已退回
|
||||||
List<OrderDetail> details = orderDetailMapper.selectByIds(detailIds);
|
OrderDetail detailCriteria = new OrderDetail();
|
||||||
|
detailCriteria.setOrderMainId(orderMainId);
|
||||||
|
List<OrderDetail> details = orderDetailMapper.select(detailCriteria);
|
||||||
for (OrderDetail detail : details) {
|
for (OrderDetail detail : details) {
|
||||||
detail.setDispenseStatus(DispenseStatus.DISPENSED.getCode());
|
detail.setStatus(OrderStatus.CANCELLED.getCode());
|
||||||
detail.setDispenseTime(dispenseTime); // 统一时间
|
detail.setUpdateTime(new Date());
|
||||||
}
|
orderDetailMapper.updateByPrimaryKeySelective(detail);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("住院发药完成,orderMainId={}, detailIds={}, dispenseTime={}",
|
// 5. 释放占用的号源(如果有排班信息)
|
||||||
orderMainId, detailIds, dispenseTime);
|
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)
|
private void releaseScheduleIfExists(OrderMain orderMain) {
|
||||||
@Override
|
if (orderMain.getScheduleSlotId() == null) {
|
||||||
public void refundInpatient(Long orderMainId, List<Long> detailIds) {
|
return;
|
||||||
Date refundTime = new Date(); // 统一时间
|
|
||||||
|
|
||||||
// 更新汇总单状态
|
|
||||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
|
||||||
if (orderMain == null) {
|
|
||||||
throw new BusinessException("未找到对应的住院医嘱主单");
|
|
||||||
}
|
}
|
||||||
orderMain.setDispenseStatus(DispenseStatus.REFUNDED.getCode());
|
// 1) 将对应的号源状态设为可用
|
||||||
orderMain.setRefundTime(refundTime);
|
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(orderMain.getScheduleSlotId());
|
||||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
if (slot != null) {
|
||||||
|
slot.setStatus(ScheduleSlotStatus.AVAILABLE.getCode());
|
||||||
// 更新明细表状态
|
slot.setUpdateTime(new Date());
|
||||||
List<OrderDetail> details = orderDetailMapper.selectByIds(detailIds);
|
scheduleSlotMapper.updateByPrimaryKeySelective(slot);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录退款日志(保持业务完整性)
|
// 2) 更新号源池的已使用计数
|
||||||
RefundLog refundLog = new RefundLog();
|
if (slot != null && slot.getSchedulePoolId() != null) {
|
||||||
refundLog.setOrderMainId(orderMainId);
|
SchedulePool pool = schedulePoolMapper.selectByPrimaryKey(slot.getSchedulePoolId());
|
||||||
refundLog.setDetailIds(StringUtils.collectionToCommaDelimitedString(detailIds));
|
if (pool != null && pool.getUsedCount() != null && pool.getUsedCount() > 0) {
|
||||||
refundLog.setRefundTime(refundTime);
|
pool.setUsedCount(pool.getUsedCount() - 1);
|
||||||
refundLogMapper.insertSelective(refundLog);
|
pool.setUpdateTime(new Date());
|
||||||
|
schedulePoolMapper.updateByPrimaryKeySelective(pool);
|
||||||
logger.info("住院退药完成,orderMainId={}, detailIds={}, refundTime={}",
|
}
|
||||||
orderMainId, detailIds, refundTime);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// 其余业务方法保持不变
|
// 其它业务实现(保持不变)
|
||||||
// -------------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
// 下面的代码保持原有实现,仅在需要使用时间的地方统一使用变量
|
|
||||||
// 如有其他 new Date() 调用,请参考上述两段实现进行统一处理
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user