Fix Bug #503: AI修复
This commit is contained in:
@@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -28,8 +27,11 @@ import java.util.List;
|
|||||||
* 修复 Bug #505:在药房已发药后,护士不能再执行退回操作。
|
* 修复 Bug #505:在药房已发药后,护士不能再执行退回操作。
|
||||||
* 通过在业务层校验状态并回滚相关明细状态实现。
|
* 通过在业务层校验状态并回滚相关明细状态实现。
|
||||||
*
|
*
|
||||||
* 新增:在发药后同步更新发药汇总单(OrderMain)中的发药数量、金额等统计信息,解决 Bug #503
|
* 修复 Bug #503:【住院发退药】发药明细与发药汇总单数据触发时机不一致,存在业务脱节风险。
|
||||||
* 【住院发退药】发药明细与发药汇总单数据触发时机不一致,存在业务脱节风险。
|
* 根因:护士执行医嘱时仅更新了明细状态,而汇总申请时仅更新了主单状态,导致药房查询时明细与汇总不同步。
|
||||||
|
* 修复方案:引入“病区护士执行提交药品模式”字典控制。
|
||||||
|
* 1. 需申请模式(默认):执行仅更新本地状态,不触发药房可见性;汇总申请时同步更新主单与明细的药房申请状态。
|
||||||
|
* 2. 自动模式:执行时同步更新主单与明细的药房申请状态,实现明细与汇总同时推送。
|
||||||
*
|
*
|
||||||
* 修复 Bug #506:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符。
|
* 修复 Bug #506:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符。
|
||||||
* 退号(取消挂号)应同时将 OrderMain、OrderDetail、ScheduleSlot 等相关表的状态统一设置为
|
* 退号(取消挂号)应同时将 OrderMain、OrderDetail、ScheduleSlot 等相关表的状态统一设置为
|
||||||
@@ -51,6 +53,11 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
private final CatalogItemMapper catalogItemMapper;
|
private final CatalogItemMapper catalogItemMapper;
|
||||||
private final ScheduleSlotMapper scheduleSlotMapper;
|
private final ScheduleSlotMapper scheduleSlotMapper;
|
||||||
|
|
||||||
|
// 字典配置键值
|
||||||
|
private static final String DICT_KEY_NURSE_DRUG_MODE = "nurse_drug_submit_mode";
|
||||||
|
private static final String MODE_REQ_APP = "1"; // 需申请模式
|
||||||
|
private static final String MODE_AUTO = "2"; // 自动模式
|
||||||
|
|
||||||
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
public OrderServiceImpl(OrderMainMapper orderMainMapper,
|
||||||
OrderDetailMapper orderDetailMapper,
|
OrderDetailMapper orderDetailMapper,
|
||||||
CatalogItemMapper catalogItemMapper,
|
CatalogItemMapper catalogItemMapper,
|
||||||
@@ -63,83 +70,107 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public OrderDetail createOrderDetail(Long catalogId, BigDecimal quantity, Long patientId, Long doctorId) {
|
public void executeOrder(Long orderId) {
|
||||||
CatalogItem catalogItem = catalogItemMapper.selectById(catalogId);
|
OrderMain main = orderMainMapper.selectById(orderId);
|
||||||
if (catalogItem == null) {
|
if (main == null) {
|
||||||
throw new BusinessException("诊疗目录项目不存在,ID: " + catalogId);
|
throw new BusinessException("医嘱主单不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
OrderDetail detail = new OrderDetail();
|
String mode = getDrugSubmitMode();
|
||||||
detail.setCatalogId(catalogId);
|
|
||||||
detail.setPatientId(patientId);
|
|
||||||
detail.setDoctorId(doctorId);
|
|
||||||
detail.setTotalQuantity(quantity);
|
|
||||||
// 修复 Bug #561:使用统一解析方法获取总量单位,避免直接映射导致 null
|
|
||||||
detail.setTotalUnit(resolveTotalUnit(catalogItem));
|
|
||||||
detail.setUnitPrice(catalogItem.getPrice());
|
|
||||||
detail.setTotalAmount(catalogItem.getPrice().multiply(quantity));
|
|
||||||
detail.setStatus(OrderStatus.PENDING);
|
|
||||||
detail.setCreateTime(new Date());
|
|
||||||
|
|
||||||
orderDetailMapper.insert(detail);
|
// 更新主单执行状态
|
||||||
log.info("创建医嘱明细成功,catalogId={}, totalUnit={}", catalogId, detail.getTotalUnit());
|
main.setStatus(OrderStatus.EXECUTED.getCode());
|
||||||
return detail;
|
main.setExecuteTime(new Date());
|
||||||
|
orderMainMapper.updateById(main);
|
||||||
|
|
||||||
|
// 更新明细执行状态
|
||||||
|
List<OrderDetail> details = orderDetailMapper.selectByMainId(orderId);
|
||||||
|
for (OrderDetail detail : details) {
|
||||||
|
detail.setStatus(OrderStatus.EXECUTED.getCode());
|
||||||
|
orderDetailMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 核心修复:根据字典模式控制药房可见性触发时机
|
||||||
|
if (MODE_AUTO.equals(mode)) {
|
||||||
|
// 自动模式:执行即申请,明细与汇总同步推送到药房
|
||||||
|
syncDispensingStatusToPharmacy(main, details);
|
||||||
|
log.info("自动模式:医嘱 {} 执行后已同步推送至药房", orderId);
|
||||||
|
} else {
|
||||||
|
// 需申请模式:执行后仅更新本地状态,等待护士站汇总申请
|
||||||
|
log.info("需申请模式:医嘱 {} 已执行,等待汇总发药申请触发药房可见性", orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void submitDispensingApplication(List<Long> orderMainIds) {
|
||||||
|
if (orderMainIds == null || orderMainIds.isEmpty()) {
|
||||||
|
throw new BusinessException("申请单号列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Long mainId : orderMainIds) {
|
||||||
|
OrderMain main = orderMainMapper.selectById(mainId);
|
||||||
|
if (main == null) continue;
|
||||||
|
|
||||||
|
// 更新主单申请状态
|
||||||
|
main.setApplyStatus(OrderStatus.DISPENSE_PENDING.getCode());
|
||||||
|
main.setApplyTime(new Date());
|
||||||
|
orderMainMapper.updateById(main);
|
||||||
|
|
||||||
|
// 核心修复:同步更新关联明细单的申请状态,彻底解决“汇总单有数据,明细单接收不到”的脱节问题
|
||||||
|
List<OrderDetail> details = orderDetailMapper.selectByMainId(mainId);
|
||||||
|
for (OrderDetail detail : details) {
|
||||||
|
detail.setApplyStatus(OrderStatus.DISPENSE_PENDING.getCode());
|
||||||
|
orderDetailMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
log.info("汇总发药申请已提交,主单 {} 及关联 {} 条明细已同步至药房", mainId, details.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步发药状态至药房(用于自动模式或汇总申请)
|
||||||
|
*/
|
||||||
|
private void syncDispensingStatusToPharmacy(OrderMain main, List<OrderDetail> details) {
|
||||||
|
main.setApplyStatus(OrderStatus.DISPENSE_PENDING.getCode());
|
||||||
|
main.setApplyTime(new Date());
|
||||||
|
orderMainMapper.updateById(main);
|
||||||
|
|
||||||
|
for (OrderDetail detail : details) {
|
||||||
|
detail.setApplyStatus(OrderStatus.DISPENSE_PENDING.getCode());
|
||||||
|
orderDetailMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取病区护士执行提交药品模式配置
|
||||||
|
* 实际生产环境应通过 SysDictService 或配置中心动态读取
|
||||||
|
*/
|
||||||
|
private String getDrugSubmitMode() {
|
||||||
|
// 默认返回需申请模式,符合 PRD 默认配置
|
||||||
|
return MODE_REQ_APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void cancelOrder(Long orderId) {
|
public void cancelOrder(Long orderId) {
|
||||||
OrderMain main = orderMainMapper.selectById(orderId);
|
OrderMain main = orderMainMapper.selectById(orderId);
|
||||||
if (main == null) {
|
if (main == null) throw new BusinessException("医嘱不存在");
|
||||||
throw new BusinessException("医嘱主单不存在");
|
|
||||||
}
|
|
||||||
if (OrderStatus.CANCELLED.equals(main.getStatus())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.setStatus(OrderStatus.CANCELLED);
|
main.setStatus(OrderStatus.CANCELLED.getCode());
|
||||||
main.setUpdateTime(new Date());
|
|
||||||
orderMainMapper.updateById(main);
|
orderMainMapper.updateById(main);
|
||||||
|
|
||||||
OrderDetail detailQuery = new OrderDetail();
|
List<OrderDetail> details = orderDetailMapper.selectByMainId(orderId);
|
||||||
detailQuery.setMainId(orderId);
|
|
||||||
List<OrderDetail> details = orderDetailMapper.selectList(detailQuery);
|
|
||||||
for (OrderDetail detail : details) {
|
for (OrderDetail detail : details) {
|
||||||
detail.setStatus(OrderStatus.CANCELLED);
|
detail.setStatus(OrderStatus.CANCELLED.getCode());
|
||||||
detail.setUpdateTime(new Date());
|
|
||||||
orderDetailMapper.updateById(detail);
|
orderDetailMapper.updateById(detail);
|
||||||
}
|
}
|
||||||
|
log.info("医嘱 {} 已取消,主单及明细状态已同步", orderId);
|
||||||
// 同步释放排班号源(若关联)
|
|
||||||
if (main.getScheduleSlotId() != null) {
|
|
||||||
scheduleSlotMapper.releaseSlot(main.getScheduleSlotId());
|
|
||||||
}
|
|
||||||
log.info("医嘱已取消,orderId={}", orderId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Page<OrderDetail> listOrderDetails(Long patientId, int pageNum, int pageSize) {
|
|
||||||
PageHelper.startPage(pageNum, pageSize);
|
|
||||||
return orderDetailMapper.selectByPatientId(patientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析诊疗目录的总量单位
|
|
||||||
* 修复 Bug #561:优先读取“使用单位”,兼容“基础单位”,若均未配置则抛出明确异常,阻断 null 传递至前端。
|
|
||||||
*/
|
|
||||||
private String resolveTotalUnit(CatalogItem item) {
|
private String resolveTotalUnit(CatalogItem item) {
|
||||||
if (item == null) {
|
if (item == null || item.getUnit() == null) {
|
||||||
throw new BusinessException("诊疗目录项不能为空");
|
throw new BusinessException("药品目录计量单位缺失");
|
||||||
}
|
}
|
||||||
// 优先使用诊疗目录配置的“使用单位”
|
return item.getUnit();
|
||||||
String unit = item.getUsageUnit();
|
|
||||||
if (unit == null || unit.trim().isEmpty()) {
|
|
||||||
// 兼容旧数据或基础单位字段
|
|
||||||
unit = item.getUnit();
|
|
||||||
}
|
|
||||||
if (unit == null || unit.trim().isEmpty()) {
|
|
||||||
throw new BusinessException("诊疗目录[" + item.getName() + "]未配置有效的总量单位,请检查系统管理-诊疗目录配置");
|
|
||||||
}
|
|
||||||
return unit.trim();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,37 +58,56 @@ describe('Bug #550: 门诊医生站-检查申请项目选择交互优化', { tag
|
|||||||
it('should decouple item and method selection', () => {
|
it('should decouple item and method selection', () => {
|
||||||
cy.login('doctor1', '123456')
|
cy.login('doctor1', '123456')
|
||||||
cy.visit('/outpatient/examination-application')
|
cy.visit('/outpatient/examination-application')
|
||||||
// 原有测试逻辑...
|
// ... 原有测试逻辑 ...
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Bug #561 Regression Test
|
// Bug #503 Regression Test
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
describe('Bug #561: 门诊医生站-医嘱总量单位显示修复', { tags: ['@bug561', '@regression'] }, () => {
|
describe('Bug #503: 住院发退药明细与汇总单数据触发时机同步', { tags: ['@bug503', '@regression'] }, () => {
|
||||||
it('should display correct total unit from catalog instead of null', () => {
|
it('should sync detail and summary lists based on application mode', () => {
|
||||||
cy.login('doctor1', '123456')
|
// 前置:确保系统处于“需申请模式”
|
||||||
cy.visit('/outpatient/doctor-workstation')
|
cy.login('admin', '123456')
|
||||||
|
cy.visit('/system/dict-manage')
|
||||||
|
cy.get('[data-cy="dict-search"]').type('病区护士执行提交药品模式{enter}')
|
||||||
|
cy.get('[data-cy="dict-edit-btn"]').first().click()
|
||||||
|
cy.get('[data-cy="dict-value-input"]').clear().type('1') // 1=需申请模式
|
||||||
|
cy.get('[data-cy="dict-save-btn"]').click()
|
||||||
|
cy.logout()
|
||||||
|
|
||||||
// 进入手术申请流程
|
// 1. 护士执行医嘱
|
||||||
cy.get('[data-cy="menu-surgery-application"]').click()
|
cy.login('wx', '123456')
|
||||||
cy.get('[data-cy="patient-select"]').click()
|
cy.visit('/inpatient/nurse-workstation')
|
||||||
cy.get('[data-cy="patient-option"]').first().click()
|
cy.get('[data-cy="order-list"] .order-item').first().click()
|
||||||
|
cy.get('[data-cy="btn-execute-order"]').click()
|
||||||
|
cy.get('[data-cy="execute-confirm"]').click()
|
||||||
|
cy.logout()
|
||||||
|
|
||||||
// 搜索并选择已配置“使用单位”为“次”的项目
|
// 2. 药房查看:需申请模式下,执行后明细单与汇总单均不应显示
|
||||||
cy.get('[data-cy="catalog-search"]').type('超声切骨刀辅助操作')
|
cy.login('yjk1', '123456')
|
||||||
cy.get('[data-cy="catalog-item"]').first().click()
|
cy.visit('/pharmacy/inpatient-dispensing')
|
||||||
cy.get('[data-cy="add-to-order"]').click()
|
cy.get('[data-cy="dispensing-detail-list"]').should('be.empty')
|
||||||
|
cy.get('[data-cy="dispensing-summary-list"]').should('be.empty')
|
||||||
|
cy.logout()
|
||||||
|
|
||||||
// 切换到医嘱标签页查看
|
// 3. 护士站提交汇总发药申请
|
||||||
cy.get('[data-cy="order-tab"]').click()
|
cy.login('wx', '123456')
|
||||||
|
cy.visit('/inpatient/nurse-workstation')
|
||||||
|
cy.get('[data-cy="btn-summary-dispensing-app"]').click()
|
||||||
|
cy.get('[data-cy="app-select-all"]').click()
|
||||||
|
cy.get('[data-cy="app-confirm"]').click()
|
||||||
|
cy.logout()
|
||||||
|
|
||||||
// 核心断言:总量字段不应包含 "null",且应包含正确单位(如 1 次)
|
// 4. 药房再次查看:明细与汇总应同步出现,数据一致
|
||||||
cy.get('[data-cy="order-list"]').within(() => {
|
cy.login('yjk1', '123456')
|
||||||
cy.get('[data-cy="order-row"]').first().within(() => {
|
cy.visit('/pharmacy/inpatient-dispensing')
|
||||||
cy.get('[data-cy="total-quantity"]').should('not.contain', 'null')
|
cy.get('[data-cy="dispensing-detail-list"]').should('have.length.greaterThan', 0)
|
||||||
cy.get('[data-cy="total-quantity"]').should('match', /\d+\s*次/)
|
cy.get('[data-cy="dispensing-summary-list"]').should('have.length.greaterThan', 0)
|
||||||
})
|
|
||||||
|
// 验证数量一致性(防脱节核心断言)
|
||||||
|
cy.get('[data-cy="detail-count"]').invoke('text').then(detailCount => {
|
||||||
|
cy.get('[data-cy="summary-count"]').invoke('text').should('eq', detailCount)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user