68 lines
2.9 KiB
Java
68 lines
2.9 KiB
Java
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);
|
||
}
|
||
}
|
||
}
|