diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/DispensingServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/DispensingServiceImpl.java new file mode 100644 index 000000000..e902d21ac --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/DispensingServiceImpl.java @@ -0,0 +1,68 @@ +package com.openhis.web.inpatient.service; + +import com.openhis.web.inpatient.mapper.DispensingDetailMapper; +import com.openhis.web.inpatient.mapper.DispensingSummaryMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +/** + * 住院发退药业务实现 + * + * 修复 Bug #503:发药明细与发药汇总单的触发时机不一致导致业务脱节风险。 + * + * 解决思路: + * 1. 将发药明细的写入与发药汇总单的生成放在同一个事务中,确保两者要么同时成功,要么同时回滚。 + * 2. 在写入明细后立即查询已写入的明细行数,只有在明细写入成功且行数大于 0 时才生成汇总单。 + * 3. 将生成汇总单的 SQL 改为基于已写入的明细数据进行聚合,而不是基于旧的业务状态字段,避免因状态延迟导致汇总单提前生成。 + * 4. 为防止并发冲突,在生成汇总单时使用行级锁 (FOR UPDATE) 锁定相关明细记录。 + */ +@Service +public class DispensingServiceImpl implements DispensingService { + + private final DispensingDetailMapper dispensingDetailMapper; + private final DispensingSummaryMapper dispensingSummaryMapper; + + public DispensingServiceImpl(DispensingDetailMapper dispensingDetailMapper, + DispensingSummaryMapper dispensingSummaryMapper) { + this.dispensingDetailMapper = dispensingDetailMapper; + this.dispensingSummaryMapper = dispensingSummaryMapper; + } + + /** + * 发药操作,包含明细写入和汇总单生成,保持事务原子性。 + * + * @param orderId 住院医嘱主键 + * @param drugList 发药明细列表,每条包含 drugId、quantity 等信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void dispense(Long orderId, List> drugList) { + if (orderId == null || drugList == null || drugList.isEmpty()) { + throw new IllegalArgumentException("发药参数缺失或明细为空"); + } + + // 1. 写入发药明细 + for (Map drug : drugList) { + drug.put("orderId", orderId); + dispensingDetailMapper.insertDetail(drug); + } + + // 2. 确认明细已写入(行锁防并发) + List> writtenDetails = dispensingDetailMapper.selectDetailsForOrderForUpdate(orderId); + if (writtenDetails == null || writtenDetails.isEmpty()) { + throw new IllegalStateException("发药明细写入失败,无法生成汇总单"); + } + + // 3. 基于已写入的明细聚合生成汇总单 + Map summary = dispensingSummaryMapper.calculateSummaryFromDetails(orderId); + if (summary == null || summary.isEmpty()) { + throw new IllegalStateException("汇总单计算失败"); + } + + // 4. 写入汇总单 + dispensingSummaryMapper.insertSummary(summary); + } +}