From 0fbaff9504a53076e3170a5d49f12647461178ed Mon Sep 17 00:00:00 2001 From: guanyu Date: Wed, 27 May 2026 01:51:33 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Bug=20#503:=20fallback=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inpatient/mapper/InpatientDrugMapper.java | 88 +++++++++++++++++ .../impl/InpatientDrugServiceImpl.java | 98 +++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/InpatientDrugMapper.java create mode 100644 openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDrugServiceImpl.java diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/InpatientDrugMapper.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/InpatientDrugMapper.java new file mode 100644 index 000000000..6c9301102 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/InpatientDrugMapper.java @@ -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 – 汇总单 UPSERT(INSERT … 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({ + "" + }) + int upsertDrugDispenseSummary(@Param("orderId") Long orderId, + @Param("drugId") Long drugId, + @Param("deltaQty") Integer deltaQty, + @Param("operator") String operator); +} diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDrugServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDrugServiceImpl.java new file mode 100644 index 000000000..f0eb8a92d --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDrugServiceImpl.java @@ -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> drugList) { + // 1. 逐条写入发药明细 + for (Map 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)使用 UPSERT(INSERT ... ON DUPLICATE KEY UPDATE) + // 通过同一批次的明细累计数量,确保汇总单始终是明细的聚合结果。 + for (Map 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> drugList) { + // 1. 先写退药明细(负数量表示退药) + for (Map 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 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); + } + } +}