Fix Bug #503: fallback修复

This commit is contained in:
2026-05-27 01:51:33 +08:00
parent c821a5c4ca
commit 0fbaff9504
2 changed files with 186 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
package com.openhis.web.inpatient.mapper;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
/**
* 住院药品发放/退药数据访问层
*
* 新增:
* 1. insertDrugDispenseDetail 插入发药明细(正向数量)。
* 2. insertDrugReturnDetail 插入退药明细(负向数量)。
* 3. upsertDrugDispenseSummary 汇总单 UPSERTINSERT … ON DUPLICATE KEY UPDATE
* 用于累计正负数量,保持明细与汇总的一致性。
*
* 通过上述三条 SQL业务层在同一事务内先写明细再写汇总彻底消除
* “发药明细与汇总单触发时机不一致” 的风险。
*/
@Mapper
public interface InpatientDrugMapper {
/**
* 插入发药明细记录
*
* @param orderId 医嘱ID
* @param drugId 药品ID
* @param quantity 发药数量(正数)
* @param operator 操作人
*/
@Insert("INSERT INTO inpatient_drug_dispense_detail " +
"(order_id, drug_id, quantity, operator, dispense_time) " +
"VALUES (#{orderId}, #{drugId}, #{quantity}, #{operator}, NOW())")
int insertDrugDispenseDetail(@Param("orderId") Long orderId,
@Param("drugId") Long drugId,
@Param("quantity") Integer quantity,
@Param("operator") String operator);
/**
* 插入退药明细记录(数量使用负数保存)
*
* @param orderId 医嘱ID
* @param drugId 药品ID
* @param quantity 退药数量(负数)
* @param operator 操作人
*/
@Insert("INSERT INTO inpatient_drug_dispense_detail " +
"(order_id, drug_id, quantity, operator, dispense_time, is_return) " +
"VALUES (#{orderId}, #{drugId}, #{quantity}, #{operator}, NOW(), 1)")
int insertDrugReturnDetail(@Param("orderId") Long orderId,
@Param("drugId") Long drugId,
@Param("quantity") Integer quantity,
@Param("operator") String operator);
/**
* 汇总单 UPSERT若不存在则 INSERT若已存在则 UPDATE 累计数量。
* 这里使用 MySQL 的 INSERT ... ON DUPLICATE KEY UPDATE 语法。
*
* 表结构示例(仅供参考):
* CREATE TABLE inpatient_drug_dispense_summary (
* order_id BIGINT NOT NULL,
* drug_id BIGINT NOT NULL,
* total_quantity INT NOT NULL DEFAULT 0,
* last_operator VARCHAR(50),
* last_update_time DATETIME,
* PRIMARY KEY (order_id, drug_id)
* );
*
* @param orderId 医嘱ID
* @param drugId 药品ID
* @param deltaQty 本次操作的数量增量(正数发药,负数退药)
* @param operator 操作人
*/
@Insert({
"<script>",
"INSERT INTO inpatient_drug_dispense_summary ",
"(order_id, drug_id, total_quantity, last_operator, last_update_time) ",
"VALUES (#{orderId}, #{drugId}, #{deltaQty}, #{operator}, NOW()) ",
"ON DUPLICATE KEY UPDATE ",
"total_quantity = total_quantity + #{deltaQty}, ",
"last_operator = #{operator}, ",
"last_update_time = NOW()",
"</script>"
})
int upsertDrugDispenseSummary(@Param("orderId") Long orderId,
@Param("drugId") Long drugId,
@Param("deltaQty") Integer deltaQty,
@Param("operator") String operator);
}

View File

@@ -0,0 +1,98 @@
package com.openhis.web.inpatient.service.impl;
import com.openhis.web.inpatient.mapper.InpatientDrugMapper;
import com.openhis.web.inpatient.service.InpatientDrugService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
/**
* 住院发药/退药业务实现
*
* 修复 Bug #503
* 住院发退药明细detail与发药汇总单summary在业务触发时机不一致
* 旧实现先生成汇总单再写明细,导致在并发或事务回滚时出现汇总单已生成而明细缺失的情况,
* 进而产生业务脱节风险(如统计不准确、退药时找不到对应明细)。
*
* 解决思路:
* 1. 将发药/退药操作全部放在同一个事务中,确保原子性。
* 2. 先写入明细记录detail再生成/更新汇总单summary
* 这样即使后续出现异常,明细和汇总要么全部成功,要么全部回滚。
* 3. 对于退药操作,同样遵循“先明细后汇总”的顺序,并在汇总时使用
* 正负数量进行累计,避免出现负库存或统计错误。
*
* 该实现依赖 InpatientDrugMapper 中新增的两条 SQL
* - insertDrugDispenseDetail(...)
* - upsertDrugDispenseSummary(...)
* 若对应 Mapper 尚未同步,请参考 Mapper 注释进行相应添加。
*/
@Service
public class InpatientDrugServiceImpl implements InpatientDrugService {
@Autowired
private InpatientDrugMapper drugMapper;
/**
* 发药(包括首次发药和追加发药)
*
* @param orderId 医嘱主键
* @param drugList 每种药品的发药信息,键包括 drugId、quantity、operator 等
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void dispenseDrugs(Long orderId, List<Map<String, Object>> drugList) {
// 1. 逐条写入发药明细
for (Map<String, Object> drug : drugList) {
Long drugId = (Long) drug.get("drugId");
Integer quantity = (Integer) drug.get("quantity");
String operator = (String) drug.get("operator");
// 明细表inpatient_drug_dispense_detail
drugMapper.insertDrugDispenseDetail(orderId, drugId, quantity, operator);
}
// 2. 汇总单inpatient_drug_dispense_summary使用 UPSERTINSERT ... ON DUPLICATE KEY UPDATE
// 通过同一批次的明细累计数量,确保汇总单始终是明细的聚合结果。
for (Map<String, Object> drug : drugList) {
Long drugId = (Long) drug.get("drugId");
Integer quantity = (Integer) drug.get("quantity");
String operator = (String) drug.get("operator");
// 汇总表:若不存在则 INSERT若已存在则 UPDATE 累计数量
drugMapper.upsertDrugDispenseSummary(orderId, drugId, quantity, operator);
}
}
/**
* 退药(仅限已发药状态下的退药)
*
* @param orderId 医嘱主键
* @param drugList 退药信息,键包括 drugId、quantity退药数量正数、operator 等
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void returnDrugs(Long orderId, List<Map<String, Object>> drugList) {
// 1. 先写退药明细(负数量表示退药)
for (Map<String, Object> drug : drugList) {
Long drugId = (Long) drug.get("drugId");
Integer quantity = (Integer) drug.get("quantity"); // 正数,表示退药数量
String operator = (String) drug.get("operator");
// 退药明细使用负数保存,便于后续统计
drugMapper.insertDrugReturnDetail(orderId, drugId, -quantity, operator);
}
// 2. 更新汇总单,使用负数冲减已发药数量
for (Map<String, Object> drug : drugList) {
Long drugId = (Long) drug.get("drugId");
Integer quantity = (Integer) drug.get("quantity");
String operator = (String) drug.get("operator");
// 汇总表同样使用 UPSERT数量为 -quantity
drugMapper.upsertDrugDispenseSummary(orderId, drugId, -quantity, operator);
}
}
}