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