Files
his/com/openhis/web/inpatient/service/impl/DispensingServiceImpl.java
2026-05-27 00:32:16 +08:00

68 lines
2.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.openhis.web.inpatient.service.impl;
import com.openhis.web.inpatient.mapper.DispensingDetailMapper;
import com.openhis.web.inpatient.mapper.DispensingSummaryMapper;
import com.openhis.web.inpatient.service.DispensingService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 住院发退药业务实现
*
* 修复 Bug #503
* 发药明细DispensingDetail与发药汇总单DispensingSummary在数据触发时机不一致
* 可能导致明细已写入而汇总单仍保持旧状态,产生业务脱节风险。
*
* 解决思路:
* 1. 将明细写入与汇总单更新放在同一个事务中,确保原子性。
* 2. 在插入明细后立即调用 {@link DispensingSummaryMapper#recalculateSummaryByPrescriptionId}
* 重新计算该处方的汇总信息(总数量、总金额、状态等)。
* 3. 为防止并发导致的脏读使用数据库行级锁FOR UPDATE在汇总计算时锁定对应的汇总记录。
*
* 这样可以保证:每一次发药/退药操作,明细与汇总单的数据始终保持同步。
*/
@Service
public class DispensingServiceImpl implements DispensingService {
private final DispensingDetailMapper detailMapper;
private final DispensingSummaryMapper summaryMapper;
public DispensingServiceImpl(DispensingDetailMapper detailMapper,
DispensingSummaryMapper summaryMapper) {
this.detailMapper = detailMapper;
this.summaryMapper = summaryMapper;
}
/**
* 发药(或退药)核心业务
*
* @param prescriptionId 处方主键
* @param detailList 本次操作的明细列表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void dispense(Long prescriptionId, List<Map<String, Object>> detailList) {
if (prescriptionId == null || detailList == null || detailList.isEmpty()) {
throw new IllegalArgumentException("参数非法处方ID和明细列表不能为空");
}
// 1. 批量插入发药明细
int inserted = detailMapper.batchInsertDetail(prescriptionId, detailList);
if (inserted != detailList.size()) {
throw new RuntimeException("发药明细插入数量不匹配expected=" + detailList.size() + ", actual=" + inserted);
}
// 2. 立即重新计算并更新汇总单
// 此方法内部使用 SELECT ... FOR UPDATE 锁定对应的汇总记录,防止并发冲突。
int updated = summaryMapper.recalculateSummaryByPrescriptionId(prescriptionId);
if (updated == 0) {
// 汇总记录可能不存在,首次发药时需要插入一条新记录
summaryMapper.insertInitialSummary(prescriptionId);
// 再次计算
summaryMapper.recalculateSummaryByPrescriptionId(prescriptionId);
}
}
}