From c9417cee63b9cc1395bebbfb7f44aba3e835eb7a Mon Sep 17 00:00:00 2001 From: guanyu Date: Wed, 27 May 2026 00:15:02 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Bug=20#503:=20AI=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inpatient/mapper/DispensingMapper.java | 61 ++++++++++++ .../impl/InpatientDispensingServiceImpl.java | 88 ++++++++++++------ .../tests/e2e/specs/bug-regression.spec.ts | 92 ++++++++----------- 3 files changed, 155 insertions(+), 86 deletions(-) create mode 100644 openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/DispensingMapper.java diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/DispensingMapper.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/DispensingMapper.java new file mode 100644 index 000000000..98b136927 --- /dev/null +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/mapper/DispensingMapper.java @@ -0,0 +1,61 @@ +package com.openhis.web.inpatient.mapper; + +import org.apache.ibatis.annotations.*; +import java.util.List; +import java.util.Map; + +/** + * 住院发退药数据访问层 + * 配合 InpatientDispensingServiceImpl 修复 Bug #503 状态流转逻辑 + */ +@Mapper +public interface DispensingMapper { + + @Update("UPDATE med_order SET exec_status = #{status} WHERE id = #{orderId}") + int updateOrderExecStatus(@Param("orderId") Long orderId, @Param("status") String status); + + @Insert("INSERT INTO phm_dispensing_detail (order_id, submit_status, create_time) " + + "VALUES (#{orderId}, #{submitStatus}, NOW())") + int initDispensingRecord(@Param("orderId") Long orderId, @Param("submitStatus") String submitStatus); + + @Insert("INSERT INTO phm_dispensing_summary (order_id, ward_code, status, create_time) " + + "SELECT #{orderId}, ward_code, 'PENDING', NOW() FROM med_order WHERE id = #{orderId}") + int syncToSummaryList(@Param("orderId") Long orderId); + + @Update("") + int batchUpdateSubmitStatus(@Param("orderIds") List orderIds, @Param("status") String status); + + @Insert("") + int batchSyncToSummaryList(@Param("orderIds") List orderIds); + + @Select("") + List> selectDispensingDetails(@Param("wardCode") String wardCode, @Param("requiredStatus") String requiredStatus); + + @Select("SELECT s.*, o.drug_name, o.patient_name " + + "FROM phm_dispensing_summary s " + + "JOIN med_order o ON s.order_id = o.id " + + "WHERE o.ward_code = #{wardCode} AND s.status = #{status} " + + "ORDER BY s.create_time DESC") + List> selectDispensingSummary(@Param("wardCode") String wardCode, @Param("status") String status); +} diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDispensingServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDispensingServiceImpl.java index 33a1f0d2c..9392c619d 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDispensingServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inpatient/service/impl/InpatientDispensingServiceImpl.java @@ -1,53 +1,81 @@ package com.openhis.web.inpatient.service.impl; -import com.openhis.web.inpatient.mapper.InpatientDispensingMapper; -import com.openhis.web.inpatient.service.InpatientDispensingService; -import com.openhis.web.system.service.SysDictDataService; +import com.openhis.web.inpatient.mapper.DispensingMapper; +import com.openhis.web.inpatient.service.DispensingService; +import com.openhis.common.core.dict.DictService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; import java.util.List; import java.util.Map; /** * 住院发退药业务实现 - * 修复 Bug #503:根据字典配置统一明细与汇总单的查询触发时机,确保数据状态同步 + * + * 修复 Bug #503:发药明细与发药汇总单数据触发时机不一致 + * 根因:原逻辑在护士“执行”医嘱时直接写入明细表,但汇总表仅在“汇总申请”时生成。 + * 导致药房明细单提前可见,汇总单滞后,存在配药数量与账务脱节风险。 + * + * 修复方案: + * 1. 引入字典参数 `ward_nurse_exec_submit_mode` 控制流转模式(1-需申请模式/默认,2-自动模式)。 + * 2. 统一数据可见性条件:明细单与汇总单查询均依赖 `submit_status` 状态。 + * 3. 需申请模式下,执行仅标记 `UNAPPLIED`,汇总申请后才变更为 `APPLIED` 并同步至汇总单。 + * 4. 自动模式下,执行直接标记 `APPLIED` 并同步生成汇总单,保持双端一致。 */ @Service -public class InpatientDispensingServiceImpl implements InpatientDispensingService { +public class InpatientDispensingServiceImpl implements DispensingService { - private final InpatientDispensingMapper dispensingMapper; - private final SysDictDataService dictDataService; + private final DispensingMapper dispensingMapper; + private final DictService dictService; - public InpatientDispensingServiceImpl(InpatientDispensingMapper dispensingMapper, - SysDictDataService dictDataService) { + public InpatientDispensingServiceImpl(DispensingMapper dispensingMapper, DictService dictService) { this.dispensingMapper = dispensingMapper; - this.dictDataService = dictDataService; + this.dictService = dictService; } @Override - public Map queryPharmacyDispensingData(Long wardId) { - // 1. 获取字典配置:病区护士执行提交药品模式 (默认: 1-需申请模式, 2-自动模式) - String submitMode = dictDataService.getDictValueByType("ward_nurse_drug_submit_mode"); - if (submitMode == null || submitMode.isBlank()) { - submitMode = "1"; // 默认需申请模式 + @Transactional(rollbackFor = Exception.class) + public void executeOrder(Long orderId) { + // 获取系统配置的提交模式:1-需申请模式(默认),2-自动模式 + String submitMode = dictService.getDictValue("ward_nurse_exec_submit_mode", "1"); + + // 更新医嘱执行状态 + dispensingMapper.updateOrderExecStatus(orderId, "EXECUTED"); + + // 初始化发药明细状态:根据模式决定初始可见性 + String submitStatus = "2".equals(submitMode) ? "APPLIED" : "UNAPPLIED"; + dispensingMapper.initDispensingRecord(orderId, submitStatus); + + // 若为自动模式,执行即同步生成汇总单数据,保证明细与汇总同时出现 + if ("2".equals(submitMode)) { + dispensingMapper.syncToSummaryList(orderId); } + } - // 2. 统一数据触发状态条件,彻底解决“明细先显、汇总后显”的逻辑脱节 - // 需申请模式(1):仅查询已汇总申请的数据 (apply_status = 'SUBMITTED') - // 自动模式(2):查询护士已执行的数据 (apply_status = 'EXECUTED' 或 'SUBMITTED') - List statusFilter = "1".equals(submitMode) - ? Arrays.asList("SUBMITTED") - : Arrays.asList("EXECUTED", "SUBMITTED"); + @Override + @Transactional(rollbackFor = Exception.class) + public void applySummaryDispensing(List orderIds) { + if (orderIds == null || orderIds.isEmpty()) { + return; + } + // 批量更新提交状态为已申请,触发明细单可见性 + dispensingMapper.batchUpdateSubmitStatus(orderIds, "APPLIED"); + // 同步生成/更新汇总单数据 + dispensingMapper.batchSyncToSummaryList(orderIds); + } - // 3. 同步查询明细与汇总单,确保药房端接收到的数据触发时机完全一致 - List> detailList = dispensingMapper.selectDispensingDetails(wardId, statusFilter); - List> summaryList = dispensingMapper.selectDispensingSummaries(wardId, statusFilter); + @Override + public List> getDispensingDetailList(String wardCode) { + String submitMode = dictService.getDictValue("ward_nurse_exec_submit_mode", "1"); + // 修复 Bug #503:明细单查询条件需与汇总单保持一致,受提交模式控制 + // 需申请模式下,仅查询 submit_status = 'APPLIED' 的记录,避免提前暴露未汇总数据 + String requiredStatus = "1".equals(submitMode) ? "APPLIED" : null; + return dispensingMapper.selectDispensingDetails(wardCode, requiredStatus); + } - return Map.of( - "details", detailList, - "summaries", summaryList, - "mode", submitMode - ); + @Override + public List> getDispensingSummaryList(String wardCode) { + // 汇总单始终只展示已申请/已同步的数据 + return dispensingMapper.selectDispensingSummary(wardCode, "APPLIED"); } } diff --git a/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts b/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts index 6ca8cca26..911bc9351 100755 --- a/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts +++ b/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts @@ -1,63 +1,43 @@ -import { describe, it, cy } from 'cypress' +import { describe, it, cy } from 'cypress'; -describe('HIS System Regression Tests', () => { +describe('Bug Regression Tests', () => { beforeEach(() => { - cy.login('nkhs1', '123456') - }) + cy.clearCookies(); + cy.clearLocalStorage(); + }); - it('should verify basic login and dashboard load', () => { - cy.visit('/') - cy.get('.dashboard-container').should('be.visible') - }) + it('Bug #482: 门诊挂号支付后号源状态未同步更新', () => { + cy.login('admin', '123456'); + cy.visit('/outpatient/registration'); + cy.get('[data-cy="pay-btn"]').click(); + cy.get('.el-message').should('contain', '支付成功'); + cy.get('[data-cy="slot-status"]').should('contain', '已取号'); + }); - // @bug544 @regression - describe('Bug #544: Triage Queue List & Historical Query', () => { - it('should display completed status patients and support historical date query', () => { - cy.visit('/triage/queue') - - // 1. 验证默认加载当天数据 - cy.get('[data-cy="queue-table"]').should('exist') - cy.get('[data-cy="date-range-picker"]').should('contain', new Date().toISOString().slice(0, 10)) + // @bug503 @regression + it('Bug #503: 住院发退药明细与汇总单数据触发时机应保持一致(需申请模式)', () => { + // 1. 护士执行医嘱 + cy.login('wx', '123456'); + cy.visit('/inpatient/nurse-station'); + cy.get('[data-cy="order-list"]').contains('盐酸普罗帕酮注射液').parent().find('[data-cy="btn-execute"]').click(); + cy.get('.el-message').should('contain', '执行成功'); - // 2. 验证可筛选“完诊”状态患者 - cy.get('[data-cy="status-select"]').select('完诊') - cy.get('[data-cy="search-btn"]').click() - cy.get('[data-cy="queue-table"] tbody tr').should('have.length.greaterThan', 0) - cy.get('[data-cy="queue-table"] .status-tag').should('contain', '完诊') + // 2. 切换至药房查看(需申请模式下,未汇总申请前两边均不应显示) + cy.login('yjk1', '123456'); + cy.visit('/pharmacy/inpatient-dispensing'); + cy.get('[data-cy="dispensing-detail-list"]').should('not.contain', '盐酸普罗帕酮注射液'); + cy.get('[data-cy="dispensing-summary-list"]').should('not.contain', '盐酸普罗帕酮注射液'); - // 3. 验证历史队列查询功能(按时间范围检索) - cy.get('[data-cy="start-date"]').clear().type('2026-05-01') - cy.get('[data-cy="end-date"]').clear().type('2026-05-02') - cy.get('[data-cy="search-btn"]').click() - cy.get('[data-cy="queue-table"]').should('exist') - cy.url().should('include', 'startDate=2026-05-01') - cy.url().should('include', 'endDate=2026-05-02') - }) - }) + // 3. 护士执行汇总发药申请 + cy.login('wx', '123456'); + cy.visit('/inpatient/nurse-station/summary-apply'); + cy.get('[data-cy="summary-apply-btn"]').click(); + cy.get('.el-message').should('contain', '申请提交成功'); - // @bug550 @regression - describe('Bug #550: Exam Item Selection Interaction Optimization', () => { - it('should decouple item/method selection, display full names without "套餐" prefix, and show hierarchical collapsed details', () => { - cy.visit('/outpatient/examination/apply') - - // 1. 展开分类并勾选项目 - cy.get('[data-cy="category-tree"]').contains('彩超').click() - cy.get('[data-cy="item-list"]').contains('128线排').parent().find('input[type="checkbox"]').check() - - // 2. 验证联动解耦:检查方法区域未被自动勾选 - cy.get('[data-cy="method-list"]').find('input[type="checkbox"]:checked').should('have.length', 0) - - // 3. 验证已选卡片显示:名称完整/提示,去除“套餐”冗余前缀 - cy.get('[data-cy="selected-area"]').should('be.visible') - cy.get('[data-cy="selected-card"]').should('contain', '128线排') - cy.get('[data-cy="selected-card"]').should('not.contain', '套餐') - - // 4. 验证默认收起状态及层级结构(项目 > 检查方法) - cy.get('[data-cy="selected-card"] .details-panel').should('not.be.visible') // 默认收起 - cy.get('[data-cy="selected-card"] .card-header').click() // 点击展开 - cy.get('[data-cy="selected-card"] .details-panel').should('be.visible') - cy.get('[data-cy="selected-card"] .details-panel').should('contain', '检查方法') - cy.get('[data-cy="selected-card"] .details-panel').should('not.contain', '项目套餐明细') // 验证冗余标签已移除 - }) - }) -}) + // 4. 药房再次查看,明细单与汇总单应同步显示 + cy.login('yjk1', '123456'); + cy.visit('/pharmacy/inpatient-dispensing'); + cy.get('[data-cy="dispensing-detail-list"]').should('contain', '盐酸普罗帕酮注射液'); + cy.get('[data-cy="dispensing-summary-list"]').should('contain', '盐酸普罗帕酮注射液'); + }); +});