Fix Bug #503: fallback修复
This commit is contained in:
38
com/openhis/web/inpatient/mapper/DispensingDetailMapper.java
Normal file
38
com/openhis/web/inpatient/mapper/DispensingDetailMapper.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.openhis.web.inpatient.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发药明细 Mapper
|
||||||
|
*
|
||||||
|
* 新增 batchInsertDetail 方法,统一使用 Map 参数,便于前端传递任意字段。
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface DispensingDetailMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入发药明细。
|
||||||
|
*
|
||||||
|
* @param prescriptionId 处方主键
|
||||||
|
* @param detailList 明细数据列表,每条记录必须包含:
|
||||||
|
* - drug_id
|
||||||
|
* - quantity
|
||||||
|
* - amount
|
||||||
|
* - type(DISPENSE / RETURN)
|
||||||
|
* @return 实际插入的记录数
|
||||||
|
*/
|
||||||
|
@Insert({
|
||||||
|
"<script>",
|
||||||
|
"INSERT INTO his_dispensing_detail (prescription_id, drug_id, quantity, amount, type, created_at)",
|
||||||
|
"VALUES",
|
||||||
|
"<foreach collection='detailList' item='item' separator=','>",
|
||||||
|
"(#{prescriptionId}, #{item.drugId}, #{item.quantity}, #{item.amount}, #{item.type}, NOW())",
|
||||||
|
"</foreach>",
|
||||||
|
"</script>"
|
||||||
|
})
|
||||||
|
int batchInsertDetail(@Param("prescriptionId") Long prescriptionId,
|
||||||
|
@Param("detailList") List<Map<String, Object>> detailList);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.openhis.web.inpatient.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发药汇总单 Mapper
|
||||||
|
*
|
||||||
|
* 新增:
|
||||||
|
* 1. {@code recalculateSummaryByPrescriptionId}:基于明细表重新计算汇总信息,并使用 FOR UPDATE 锁定行。
|
||||||
|
* 2. {@code insertInitialSummary}:在首次发药时插入空的汇总记录,防止后续更新失败。
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface DispensingSummaryMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算指定处方的发药汇总信息。
|
||||||
|
*
|
||||||
|
* 该 SQL 会:
|
||||||
|
* - 对 his_dispensing_detail 按 prescription_id 汇总数量、金额等。
|
||||||
|
* - 使用 SELECT ... FOR UPDATE 锁定对应的 his_dispensing_summary 行,确保并发安全。
|
||||||
|
* - 更新汇总表的 total_quantity、total_amount、status 等字段。
|
||||||
|
*
|
||||||
|
* @param prescriptionId 处方主键
|
||||||
|
* @return 更新的记录数(通常为 1)
|
||||||
|
*/
|
||||||
|
@Update({
|
||||||
|
"<script>",
|
||||||
|
"UPDATE his_dispensing_summary s",
|
||||||
|
"SET",
|
||||||
|
" s.total_quantity = (SELECT IFNULL(SUM(d.quantity),0) FROM his_dispensing_detail d WHERE d.prescription_id = #{prescriptionId}),",
|
||||||
|
" s.total_amount = (SELECT IFNULL(SUM(d.amount),0) FROM his_dispensing_detail d WHERE d.prescription_id = #{prescriptionId}),",
|
||||||
|
" s.status = CASE",
|
||||||
|
" WHEN EXISTS (SELECT 1 FROM his_dispensing_detail d WHERE d.prescription_id = #{prescriptionId} AND d.type = 'RETURN')",
|
||||||
|
" THEN 'PARTIAL_RETURN'",
|
||||||
|
" ELSE 'DISPENSED'",
|
||||||
|
" END",
|
||||||
|
"WHERE s.prescription_id = #{prescriptionId}",
|
||||||
|
// 加锁,防止并发更新导致汇总不一致
|
||||||
|
"AND EXISTS (SELECT 1 FROM his_dispensing_summary s2 WHERE s2.id = s.id FOR UPDATE)",
|
||||||
|
"</script>"
|
||||||
|
})
|
||||||
|
int recalculateSummaryByPrescriptionId(@Param("prescriptionId") Long prescriptionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首次发药时插入一条空的汇总记录。
|
||||||
|
*
|
||||||
|
* @param prescriptionId 处方主键
|
||||||
|
*/
|
||||||
|
@Insert("INSERT INTO his_dispensing_summary (prescription_id, total_quantity, total_amount, status) " +
|
||||||
|
"VALUES (#{prescriptionId}, 0, 0, 'INIT')")
|
||||||
|
void insertInitialSummary(@Param("prescriptionId") Long prescriptionId);
|
||||||
|
}
|
||||||
18
com/openhis/web/inpatient/service/DispensingService.java
Normal file
18
com/openhis/web/inpatient/service/DispensingService.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package com.openhis.web.inpatient.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 住院发退药业务接口
|
||||||
|
*/
|
||||||
|
public interface DispensingService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发药或退药核心业务,确保明细与汇总单同步。
|
||||||
|
*
|
||||||
|
* @param prescriptionId 处方ID
|
||||||
|
* @param detailList 本次操作的明细列表
|
||||||
|
*/
|
||||||
|
void dispense(Long prescriptionId, List<Map<String, Object>> detailList);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user