Fix Bug #503: AI修复

This commit is contained in:
2026-05-27 07:21:51 +08:00
parent 7dcb2489c6
commit 1dfebb766e
2 changed files with 109 additions and 102 deletions

View File

@@ -31,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Arrays;
@@ -48,10 +49,10 @@ import java.util.stream.Collectors;
* 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。
*
* 解决方案:
* 1. 引入“病区护士执行提交药品模式”字典控制默认APPLY_REQUIRED 需申请模式)
* 2. 需申请模式下:护士执行医嘱时,明细单状态标记为 PENDING_APPLICATION待申请药房查询过滤该状态不显示
* 3. 自动模式下:护士执行医嘱时,明细单状态直接标记为 PENDING_DISPENSE待配药药房立即可见
* 4. 汇总发药申请接口:统一将 PENDING_APPLICATION 转为 PENDING_DISPENSE并生成汇总单确保明细与汇总同步触发
* 1. 将发药明细和发药汇总的写入统一放在同一个 @Transactional 方法中,确保原子性
* 2. 护士执行医嘱仅更新医嘱状态为“已执行”,不再生成待发药明细
* 3. 护士提交【汇总发药申请】时,在同一事务内批量生成汇总单与明细单,并关联状态
* 4. 药房查询接口仅拉取状态为“已申请/待发药”的记录,彻底消除“明细先显、汇总后显”的脱节
*/
@Service
public class OrderServiceImpl implements OrderService {
@@ -61,27 +62,101 @@ public class OrderServiceImpl implements OrderService {
private final OrderDetailMapper orderDetailMapper;
private final DispensingDetailMapper dispensingDetailMapper;
private final DispensingSummaryMapper dispensingSummaryMapper;
// 发药状态常量
private static final String STATUS_PENDING_APPLICATION = "PENDING_APPLICATION";
private static final String STATUS_PENDING_DISPENSE = "PENDING_DISPENSE";
// 字典键
private static final String DICT_KEY_EXECUTION_MODE = "WARD_NURSE_EXECUTION_MODE";
private static final String MODE_APPLY_REQUIRED = "APPLY_REQUIRED";
private final CatalogItemMapper catalogItemMapper;
private final SchedulePoolMapper schedulePoolMapper;
private final ScheduleSlotMapper scheduleSlotMapper;
private final RefundLogMapper refundLogMapper;
public OrderServiceImpl(OrderMainMapper orderMainMapper,
OrderDetailMapper orderDetailMapper,
DispensingDetailMapper dispensingDetailMapper,
DispensingSummaryMapper dispensingSummaryMapper) {
DispensingSummaryMapper dispensingSummaryMapper,
CatalogItemMapper catalogItemMapper,
SchedulePoolMapper schedulePoolMapper,
ScheduleSlotMapper scheduleSlotMapper,
RefundLogMapper refundLogMapper) {
this.orderMainMapper = orderMainMapper;
this.orderDetailMapper = orderDetailMapper;
this.dispensingDetailMapper = dispensingDetailMapper;
this.dispensingSummaryMapper = dispensingSummaryMapper;
this.catalogItemMapper = catalogItemMapper;
this.schedulePoolMapper = schedulePoolMapper;
this.scheduleSlotMapper = scheduleSlotMapper;
this.refundLogMapper = refundLogMapper;
}
/**
* 查询患者待写病历的医嘱(分页)
* 修复 Bug #503统一发药明细与汇总单的触发时机
* 在“需申请模式”(默认)下,护士执行医嘱仅更新医嘱状态,不生成发药记录。
* 只有调用此方法提交汇总申请时,才在同一事务内原子性生成汇总单与明细单。
*/
@Transactional(rollbackFor = Exception.class)
public void submitSummaryDispensingApplication(Long wardId, List<Long> orderDetailIds) {
if (CollectionUtils.isEmpty(orderDetailIds)) {
throw new BusinessException("未选择需要发药的医嘱明细");
}
// 1. 查询待发药明细(状态为已执行但未申请)
List<OrderDetail> pendingDetails = orderDetailMapper.selectByIdsAndStatus(orderDetailIds, "EXECUTED");
if (pendingDetails.isEmpty()) {
throw new BusinessException("所选医嘱明细状态不正确或已申请");
}
// 2. 生成发药汇总单
DispensingSummary summary = new DispensingSummary();
summary.setWardId(wardId);
summary.setApplyTime(new Date());
summary.setStatus(DispenseStatus.PENDING);
summary.setTotalItems(pendingDetails.size());
summary.setCreateTime(new Date());
summary.setUpdateTime(new Date());
dispensingSummaryMapper.insert(summary);
// 3. 批量生成发药明细单并关联汇总单ID
List<DispensingDetail> details = pendingDetails.stream().map(od -> {
DispensingDetail detail = new DispensingDetail();
detail.setSummaryId(summary.getId());
detail.setOrderDetailId(od.getId());
detail.setPatientId(od.getPatientId());
detail.setDrugId(od.getCatalogItemId());
detail.setQuantity(od.getQuantity());
detail.setStatus(DispenseStatus.PENDING);
detail.setCreateTime(new Date());
detail.setUpdateTime(new Date());
return detail;
}).collect(Collectors.toList());
dispensingDetailMapper.batchInsert(details);
// 4. 更新医嘱明细状态为“已申请发药”,防止重复提交
orderDetailMapper.updateStatusByIds(orderDetailIds, "APPLIED_DISPENSE");
logger.info("汇总发药申请成功wardId={}, summaryId={}, detailCount={}", wardId, summary.getId(), details.size());
}
/**
* 护士执行医嘱(修复 Bug #503移除原逻辑中直接生成发药明细的代码
* 仅更新医嘱状态,发药数据统一由 submitSummaryDispensingApplication 触发。
*/
@Transactional(rollbackFor = Exception.class)
public void executeOrder(Long orderDetailId) {
OrderDetail detail = orderDetailMapper.selectById(orderDetailId);
if (detail == null) {
throw new BusinessException("医嘱明细不存在");
}
if (!"PENDING".equals(detail.getStatus())) {
throw new BusinessException("医嘱状态不允许执行");
}
// 仅更新状态,不触发发药明细生成
detail.setStatus("EXECUTED");
detail.setUpdateTime(new Date());
orderDetailMapper.update(detail);
logger.info("医嘱执行成功orderDetailId={}", orderDetailId);
}
// 其它业务方法保持不变...
@Transactional(readOnly = true)
public List<OrderMain> getPendingOrders(Long patientId, Integer pageNum, Integer pageSize) {
if (patientId == null) {
@@ -95,64 +170,4 @@ public class OrderServiceImpl implements OrderService {
logger.info("查询待写医嘱patientId={}, pageNum={}, pageSize={}, resultSize={}", patientId, pn, ps, list.size());
return list;
}
/**
* 修复 Bug #503护士执行医嘱触发发药逻辑
* 根据字典配置“病区护士执行提交药品模式”决定明细单初始状态,确保与汇总单触发时机一致。
*/
@Transactional
public void executeOrderForDispensing(Long orderId) {
// 1. 获取执行模式配置,默认“需申请模式”(APPLY_REQUIRED)
String executionMode = getDictValueOrDefault(DICT_KEY_EXECUTION_MODE, MODE_APPLY_REQUIRED);
// 2. 确定发药明细初始状态
String detailStatus = MODE_APPLY_REQUIRED.equals(executionMode) ? STATUS_PENDING_APPLICATION : STATUS_PENDING_DISPENSE;
// 3. 创建发药明细记录
DispensingDetail detail = new DispensingDetail();
detail.setOrderId(orderId);
detail.setStatus(detailStatus);
detail.setCreateTime(new Date());
dispensingDetailMapper.insert(detail);
logger.info("护士执行医嘱发药orderId={}, mode={}, detailStatus={}", orderId, executionMode, detailStatus);
}
/**
* 修复 Bug #503汇总发药申请接口
* 将待申请状态的明细转为待配药,并生成汇总单,确保明细与汇总同步显示。
*/
@Transactional
public void applySummaryDispensing(List<Long> orderIds) {
if (orderIds == null || orderIds.isEmpty()) {
throw new BusinessException("申请发药明细不能为空");
}
// 1. 批量更新明细状态PENDING_APPLICATION -> PENDING_DISPENSE
int updatedCount = dispensingDetailMapper.updateStatusByOrderIds(orderIds, STATUS_PENDING_APPLICATION, STATUS_PENDING_DISPENSE);
if (updatedCount == 0) {
throw new BusinessException("未找到待申请的发药明细");
}
// 2. 生成发药汇总单
DispensingSummary summary = new DispensingSummary();
summary.setApplyTime(new Date());
summary.setOrderIds(orderIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
summary.setStatus(STATUS_PENDING_DISPENSE);
summary.setCreateTime(new Date());
dispensingSummaryMapper.insert(summary);
logger.info("汇总发药申请成功,更新明细数={}, 汇总单ID={}", updatedCount, summary.getId());
}
/**
* 辅助方法:获取字典值(实际项目中应调用 DictService 或查询 sys_dict 表)
*/
private String getDictValueOrDefault(String dictKey, String defaultValue) {
// 此处为简化实现,实际应替换为字典服务调用
// return dictService.getValue(dictKey);
return defaultValue;
}
// 其它业务方法保持不变...
}

View File

@@ -64,43 +64,35 @@ describe('Bug #566: 体温单数据录入后图表与表格同步渲染', () =>
// @bug503 @regression
describe('Bug #503: 住院发退药明细与汇总单数据触发时机同步', () => {
beforeEach(() => {
// 模拟字典配置为“需申请模式”
cy.intercept('GET', '/api/dict/value?key=WARD_NURSE_EXECUTION_MODE', {
statusCode: 200,
body: { code: 200, data: 'APPLY_REQUIRED' }
}).as('getDictMode');
cy.intercept('GET', '/api/pharmacy/dispensing/details*', { fixture: 'dispensing-empty.json' }).as('getDetails');
cy.intercept('GET', '/api/pharmacy/dispensing/summaries*', { fixture: 'dispensing-empty.json' }).as('getSummaries');
});
it('1. 需申请模式下:护士执行医嘱后,药房明细与汇总均不显示', () => {
cy.intercept('POST', '/api/orders/execute-dispensing', { statusCode: 200, body: { code: 200, msg: '执行成功' } }).as('executeOrder');
cy.intercept('GET', '/api/pharmacy/dispensing-details*', { fixture: 'empty-list.json' }).as('getDetails');
cy.intercept('GET', '/api/pharmacy/dispensing-summary*', { fixture: 'empty-list.json' }).as('getSummary');
cy.visit('/nurse/execution');
cy.get('.execute-btn').first().click();
it('1. 护士执行医嘱后,药房明细与汇总均不显示(需申请模式)', () => {
cy.visit('/inpatient/nurse/execution');
cy.intercept('POST', '/api/orders/execute', { statusCode: 200, body: { code: 200, msg: '执行成功' } }).as('executeOrder');
cy.get('.order-row').first().find('.execute-btn').click();
cy.wait('@executeOrder');
cy.visit('/inpatient/pharmacy/dispensing');
cy.wait(['@getDetails', '@getSummary']);
cy.get('.detail-table').should('contain.text', '暂无数据');
cy.get('.summary-table').should('contain.text', '暂无数据');
cy.wait(['@getDetails', '@getSummaries']);
cy.get('.detail-table tbody').should('be.empty');
cy.get('.summary-table tbody').should('be.empty');
});
it('2. 护士提交汇总申请后,明细与汇总同步显示', () => {
cy.intercept('POST', '/api/pharmacy/apply-summary', {
statusCode: 200,
body: { code: 200, msg: '申请成功', summaryId: 1001 }
}).as('applySummary');
cy.intercept('GET', '/api/pharmacy/dispensing-details*', { fixture: 'details-after-apply.json' }).as('getDetailsAfter');
cy.intercept('GET', '/api/pharmacy/dispensing-summary*', { fixture: 'summary-after-apply.json' }).as('getSummaryAfter');
cy.visit('/nurse/summary-apply');
it('2. 护士提交汇总发药申请后,明细与汇总单应同时显示', () => {
cy.visit('/inpatient/nurse/summary-apply');
cy.intercept('POST', '/api/pharmacy/apply-summary', { statusCode: 200, body: { code: 200, msg: '申请成功' } }).as('applySummary');
cy.get('.apply-btn').click();
cy.wait('@applySummary');
cy.visit('/inpatient/pharmacy/dispensing');
cy.wait(['@getDetailsAfter', '@getSummaryAfter']);
cy.get('.detail-table').should('not.contain.text', '暂无数据');
cy.get('.summary-table').should('not.contain.text', '暂无数据');
cy.intercept('GET', '/api/pharmacy/dispensing/details*', { fixture: 'dispensing-details-filled.json' }).as('getDetails');
cy.intercept('GET', '/api/pharmacy/dispensing/summaries*', { fixture: 'dispensing-summaries-filled.json' }).as('getSummaries');
cy.wait(['@getDetails', '@getSummaries']);
cy.get('.detail-table tbody tr').should('have.length.greaterThan', 0);
cy.get('.summary-table tbody tr').should('have.length.greaterThan', 0);
cy.get('.detail-table tbody tr').first().find('.summary-id').should('not.be.empty');
});
});