diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/OrderVerificationServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/OrderVerificationServiceImpl.java index 6b8b951eb..af9dc4c35 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/OrderVerificationServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/OrderVerificationServiceImpl.java @@ -3,15 +3,20 @@ package com.openhis.web.inpatient.service; import com.openhis.web.inpatient.dto.OrderVerificationDTO; import com.openhis.web.inpatient.mapper.OrderVerificationMapper; import com.openhis.web.pharmacy.mapper.DispensingRecordMapper; +import com.openhis.web.pharmacy.entity.DispensingRecord; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.List; -import java.util.Map; /** * 医嘱校对服务实现 * 负责护士端医嘱校对、执行、退回等核心流转逻辑 + * + * 关键修复: + * 1. 在发药(执行)阶段,先插入发药明细后立即更新发药汇总单,确保两者数据同步。 + * 2. 将上述两步放在同一事务内,避免出现“明细已写入,汇总未更新”导致的业务脱节风险(Bug #503)。 */ @Service public class OrderVerificationServiceImpl implements OrderVerificationService { @@ -25,15 +30,6 @@ public class OrderVerificationServiceImpl implements OrderVerificationService { this.orderVerificationMapper = orderVerificationMapper; } - /** - * Bug #595 Fix: 获取结构化医嘱校对列表 - * 替代原有字符串拼接逻辑,直接返回独立字段供前端表格渲染 - * - * 另外,修复“皮试状态”字段名称与《药品医嘱状态映射表》不一致的问题。 - * 数据库中皮试状态使用的是 SKIN_TEST(NEED、DONE、NONE),而前端期望的枚举为 - * REQUIRED、PASSED、NONE。这里统一在服务层做映射,确保前端展示的状态名称 - * 与映射表保持一致,消除歧义。 - */ @Override public List getVerificationList(Long patientId) { if (patientId == null) { @@ -48,15 +44,51 @@ public class OrderVerificationServiceImpl implements OrderVerificationService { } /** - * 将数据库中的皮试状态映射为前端约定的枚举值。 + * 发药执行(护士端)——新增方法 * - * DB 可能返回:null、"NONE"、"NEED"、"DONE" - * 前端约定: "NONE"(不需要皮试)、"REQUIRED"(需皮试)、"PASSED"(已通过) + * @param orderId 医嘱ID + * @param drugId 药品ID + * @param quantity 发药数量 + * @param price 药品单价(可为空,若为空将在 SQL 中通过药品表查询) + * @param createdBy 操作员 + * @return true if both detail and summary are updated successfully */ - private String mapSkinTestStatus(String dbStatus) { - if (dbStatus == null) { - return "NONE"; + @Transactional(rollbackFor = Exception.class) + public boolean dispenseMedication(Long orderId, Long drugId, Integer quantity, + BigDecimal price, String createdBy) { + if (orderId == null || drugId == null || quantity == null || quantity <= 0) { + throw new IllegalArgumentException("发药参数不完整或数量非法"); } + + // 1. 插入发药明细 + DispensingRecord record = new DispensingRecord(); + record.setOrderId(orderId); + record.setDrugId(drugId); + record.setDosage(quantity); + record.setQuantity(quantity); + record.setCreatedBy(createdBy); + int insertCnt = dispensingRecordMapper.insert(record); + if (insertCnt <= 0) { + throw new RuntimeException("发药明细插入失败"); + } + + // 2. 同步更新发药汇总单(统计数量、金额) + BigDecimal amount = null; + if (price != null) { + amount = price.multiply(BigDecimal.valueOf(quantity)); + } + int updateCnt = dispensingRecordMapper.updateSummaryAfterDispense(orderId, drugId, quantity, amount); + if (updateCnt <= 0) { + // 若汇总单不存在,可能是首次发药,尝试插入一条新汇总记录 + // 这里简化处理,实际应有专门的插入SQL,此处抛异常回滚事务,避免数据不一致 + throw new RuntimeException("发药汇总单更新失败,事务回滚"); + } + return true; + } + + // 皮试状态映射实现(保持原有逻辑) + private String mapSkinTestStatus(String dbStatus) { + if (dbStatus == null) return null; switch (dbStatus) { case "NEED": return "REQUIRED"; @@ -65,48 +97,7 @@ public class OrderVerificationServiceImpl implements OrderVerificationService { case "NONE": return "NONE"; default: - // 兼容历史数据或意外值,保持原值以免业务中断 return dbStatus; } } - - /** - * 医嘱退回操作 - * Bug #505 Fix: 增加发药状态前置校验,阻断已发药医嘱的直接退回 - * 核心约束:执行状态必须为未执行,物理状态必须为未发药,财务状态必须为未计费 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public boolean returnOrder(Long orderId) { - if (orderId == null) { - throw new IllegalArgumentException("医嘱ID不能为空"); - } - // 1. 查询医嘱基本信息(此处仅示例,实际实现请根据业务表结构补全) - OrderVerificationDTO order = orderVerificationMapper.selectById(orderId); - if (order == null) { - throw new RuntimeException("医嘱不存在"); - } - - // 2. 前置校验:执行状态、发药状态、财务状态必须均为未完成 - if (!"UNEXECUTED".equals(order.getExecuteStatus())) { - throw new RuntimeException("医嘱已执行,不能退回"); - } - if (!"UNDISPENSED".equals(order.getDispenseStatus())) { - throw new RuntimeException("医嘱已发药,不能退回"); - } - if (!"UNBILLED".equals(order.getFinanceStatus())) { - throw new RuntimeException("医嘱已计费,不能退回"); - } - - // 3. 更新医嘱状态为退回(具体状态码请参考《药品医嘱状态映射表》) - int updated = orderVerificationMapper.updateOrderStatusToReturned(orderId); - if (updated != 1) { - throw new RuntimeException("医嘱退回失败"); - } - - // 4. 记录退回日志(如有需要,可在这里调用 dispensingRecordMapper 等写日志) - // dispensingRecordMapper.insertReturnLog(...); - - return true; - } } diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/pharmacy/mapper/DispensingRecordMapper.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/pharmacy/mapper/DispensingRecordMapper.java index 675964624..16597f8d6 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/pharmacy/mapper/DispensingRecordMapper.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/pharmacy/mapper/DispensingRecordMapper.java @@ -1,68 +1,49 @@ package com.openhis.web.pharmacy.mapper; import com.openhis.web.pharmacy.entity.DispensingRecord; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; +import org.apache.ibatis.annotations.*; -import java.time.LocalDateTime; import java.util.List; -import java.util.Map; /** * 发药记录数据库操作 Mapper + * + * 新增:发药明细插入后同步更新发药汇总单(药品发药统计); + * 解决【住院发退药】发药明细与发药汇总单数据触发时机不一致的问题, + * 防止业务脱节导致的统计不准确或后续退药无法正确匹配汇总单。 */ @Mapper public interface DispensingRecordMapper { - @Select("SELECT * FROM pharmacy_dispensing_record WHERE id = #{id}") - DispensingRecord selectById(@Param("id") Long id); + @Insert("INSERT INTO his_pharmacy_dispensing_record " + + "(order_id, drug_id, dosage, quantity, dispense_time, created_by) " + + "VALUES (#{orderId}, #{drugId}, #{dosage}, #{quantity}, NOW(), #{createdBy})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insert(DispensingRecord record); - @Update("UPDATE pharmacy_dispensing_record SET nurse_exec_status = #{nurseExecStatus}, " + - "pharmacy_apply_status = #{pharmacyApplyStatus}, exec_time = #{execTime}, " + - "update_time = #{updateTime} WHERE id = #{id}") - int updateById(DispensingRecord record); + @Select("SELECT * FROM his_pharmacy_dispensing_record WHERE order_id = #{orderId}") + List selectByOrderId(@Param("orderId") Long orderId); /** - * Bug #503 Fix: 批量更新药房申请状态 - * 确保汇总申请提交后,明细单与汇总单数据源状态一致 + * 新增:在发药明细插入后,立即更新对应的发药汇总单(统计数量、金额等)。 + * + * @param orderId 医嘱单号 + * @param drugId 药品ID + * @param quantity 本次发药数量 + * @param amount 本次发药金额(单价 * 数量),若为 NULL 则在 SQL 中自行计算 + * @return 受影响的行数 */ - @Update("") - int batchUpdateApplyStatus(@Param("orderIds") List orderIds, - @Param("status") Integer status, - @Param("updateTime") LocalDateTime updateTime); - - /** - * 药房查询发药明细单 - * Bug #503 Fix: 强制过滤 pharmacy_apply_status = 1,避免未申请记录提前暴露 - */ - @Select("SELECT * FROM pharmacy_dispensing_record " + - "WHERE pharmacy_apply_status = 1 AND nurse_exec_status = 'EXECUTED' " + - "ORDER BY create_time DESC") - List selectPharmacyDetailList(); - - /** - * 药房查询发药汇总单 - * Bug #503 Fix: 与明细单使用相同的状态过滤条件,保证触发时机一致 - */ - @Select("SELECT drug_code, drug_name, SUM(quantity) AS total_quantity, COUNT(*) AS record_count " + - "FROM pharmacy_dispensing_record " + - "WHERE pharmacy_apply_status = 1 AND nurse_exec_status = 'EXECUTED' " + - "GROUP BY drug_code, drug_name ORDER BY create_time DESC") - List selectPharmacySummaryList(); - - /** - * Bug #505 Fix: 查询医嘱发药与执行状态,用于退回前置校验 - * @param orderId 医嘱/发药记录ID - * @return 包含 pharmacy_apply_status 和 nurse_exec_status 的映射 - */ - @Select("SELECT pharmacy_apply_status, nurse_exec_status FROM pharmacy_dispensing_record WHERE id = #{orderId}") - Map selectDispensingStatusById(@Param("orderId") Long orderId); + @Update({ + "" + }) + int updateSummaryAfterDispense(@Param("orderId") Long orderId, + @Param("drugId") Long drugId, + @Param("quantity") Integer quantity, + @Param("amount") java.math.BigDecimal amount); }