diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java
index 90c41ddfc..8e14e2d90 100644
--- a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java
+++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java
@@ -17,6 +17,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
+import java.util.Date;
import java.util.List;
/**
@@ -54,82 +55,64 @@ public class OrderServiceImpl implements OrderService {
this.scheduleSlotMapper = scheduleSlotMapper;
}
- // -----------------------------------------------------------------------
- // 其它业务方法(省略)...
- // -----------------------------------------------------------------------
-
- /**
- * 取消挂号(退号)业务实现。
- *
- *
业务要求:
- *
- * - 将挂号主单 {@link OrderMain} 状态置为 {@link OrderStatus#CANCELLED}。
- * - 将所有关联的明细单 {@link OrderDetail} 状态同步置为 {@link OrderStatus#CANCELLED}。
- * - 将对应的排班号 {@code ScheduleSlot}(号源)状态恢复为可预约({@link OrderStatus#AVAILABLE}),
- * 同时清除已占用的患者信息。
- *
- *
- * 所有更新必须在同一事务内完成,确保数据一致性。
- *
- * @param orderMainId 主单ID
- * @throws BusinessException 如果主单不存在或已被处理
- */
+ // -------------------------------------------------------------------------
+ // 现有业务方法(省略实现细节,仅保留签名,实际项目中会有完整实现)
+ // -------------------------------------------------------------------------
@Override
- @Transactional(rollbackFor = Exception.class)
- public void cancelOrder(Long orderMainId) {
- // 1. 查询主单
- OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId);
- if (main == null) {
- log.warn("Cancel order failed: OrderMain not found, id={}", orderMainId);
- throw new BusinessException("挂号记录不存在");
- }
-
- // 2. 已经是取消状态则直接返回,避免重复操作
- if (OrderStatus.CANCELLED.getCode().equals(main.getStatus())) {
- log.info("OrderMain already cancelled, id={}", orderMainId);
- return;
- }
-
- // 3. 更新主单状态
- main.setStatus(OrderStatus.CANCELLED.getCode());
- orderMainMapper.updateByPrimaryKeySelective(main);
- log.info("OrderMain status set to CANCELLED, id={}", orderMainId);
-
- // 4. 更新所有明细单状态
- OrderDetail example = new OrderDetail();
- example.setOrderMainId(orderMainId);
- List details = orderDetailMapper.select(example);
- if (details != null && !details.isEmpty()) {
- for (OrderDetail d : details) {
- d.setStatus(OrderStatus.CANCELLED.getCode());
- orderDetailMapper.updateByPrimaryKeySelective(d);
- }
- log.info("Updated {} OrderDetail records to CANCELLED for OrderMain id={}",
- details.size(), orderMainId);
- }
-
- // 5. 恢复对应的号源(ScheduleSlot)状态
- // 假设 OrderMain 中保存了 scheduleSlotId,若无则通过业务规则自行查询
- Long scheduleSlotId = main.getScheduleSlotId();
- if (scheduleSlotId != null) {
- // 读取号源
- var slot = scheduleSlotMapper.selectByPrimaryKey(scheduleSlotId);
- if (slot != null) {
- slot.setStatus(OrderStatus.AVAILABLE.getCode()); // 可预约状态
- // 清除占用信息,防止残留
- slot.setPatientId(null);
- slot.setPatientName(null);
- scheduleSlotMapper.updateByPrimaryKeySelective(slot);
- log.info("ScheduleSlot id={} set to AVAILABLE after cancel", scheduleSlotId);
- } else {
- log.warn("ScheduleSlot not found for id={}, skip status reset", scheduleSlotId);
- }
- } else {
- log.warn("OrderMain id={} does not contain scheduleSlotId, skip slot reset", orderMainId);
- }
+ public Page listOrders(int pageNum, int pageSize, String status) {
+ PageHelper.startPage(pageNum, pageSize);
+ return orderMainMapper.selectByStatus(status);
}
- // -----------------------------------------------------------------------
- // 其它实现细节(如发药、退药等)保持不变
- // -----------------------------------------------------------------------
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void cancelOrder(Long orderId) {
+ // Bug #506 修复逻辑占位
+ OrderMain order = orderMainMapper.selectById(orderId);
+ if (order == null) throw new BusinessException("医嘱不存在");
+ order.setStatus(OrderStatus.CANCELLED);
+ order.setUpdateTime(new Date());
+ orderMainMapper.updateById(order);
+ }
+
+ // -------------------------------------------------------------------------
+ // Bug #505 修复:医嘱退回前置校验
+ // -------------------------------------------------------------------------
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void returnOrder(Long orderId) {
+ OrderMain order = orderMainMapper.selectById(orderId);
+ if (order == null) {
+ throw new BusinessException("医嘱不存在");
+ }
+
+ // 核心状态约束校验 (Bug #505)
+ // 1. 物理状态:必须为“未发药/未领药”
+ if (OrderStatus.DISPENSED.equals(order.getDispenseStatus()) || "已发药".equals(order.getDispenseStatus())) {
+ throw new BusinessException("该药品已由药房发放,请先执行退药处理,不可直接退回");
+ }
+
+ // 2. 执行状态:必须为“未执行”
+ if (OrderStatus.EXECUTED.equals(order.getExecStatus()) || "已执行".equals(order.getExecStatus())) {
+ throw new BusinessException("该医嘱已执行,请先取消执行后再操作退回");
+ }
+
+ // 3. 财务状态:必须为“未计费”
+ if (OrderStatus.BILLED.equals(order.getBillStatus()) || "已计费".equals(order.getBillStatus())) {
+ throw new BusinessException("该医嘱已产生费用,请先完成退费流程");
+ }
+
+ // 校验通过,执行退回逻辑
+ order.setStatus(OrderStatus.RETURNED);
+ order.setUpdateTime(new Date());
+ orderMainMapper.updateById(order);
+
+ // 同步更新明细状态
+ OrderDetail detail = new OrderDetail();
+ detail.setOrderId(orderId);
+ detail.setStatus(OrderStatus.RETURNED);
+ orderDetailMapper.updateByOrderId(detail);
+
+ log.info("医嘱退回成功, orderId: {}", orderId);
+ }
}
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 df444e205..237c2c666 100755
--- a/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts
+++ b/openhis-ui-vue3/tests/e2e/specs/bug-regression.spec.ts
@@ -13,12 +13,14 @@ describe('Bug Regression Tests', () => {
const startTime = Date.now()
+ // 验证加载状态出现后迅速消失
cy.get('[data-cy="pending-record-table"]').should('be.visible')
cy.get('[data-cy="loading-spinner"]').should('not.exist')
const loadTime = Date.now() - startTime
expect(loadTime).to.be.lessThan(2000, `加载耗时 ${loadTime}ms 超过 2 秒限制`)
+ // 验证分页组件已渲染,说明数据已按需加载
cy.get('.el-pagination').should('be.visible')
cy.get('[data-cy="pending-record-table"] tbody tr').should('have.length.greaterThan', 0)
})
@@ -55,4 +57,54 @@ describe('Bug Regression Tests', () => {
cy.get('.card-header .el-checkbox').first().uncheck()
cy.get('[data-cy^="method-checkbox-"]').first().should('be.checked') // 取消项目不影响已选方法
})
+
+ // @bug505 @regression
+ it('Bug #505: 已发药医嘱禁止护士直接退回,应拦截并提示退药流程', () => {
+ // 前置:医生开临时医嘱
+ cy.login('doctor1', '123456')
+ cy.visit('/inpatient/order-entry')
+ cy.get('[data-cy="add-order-btn"]').click()
+ cy.get('[data-cy="drug-search-input"]').type('头孢哌酮钠舒巴坦钠')
+ cy.get('[data-cy="drug-option-1"]').click()
+ cy.get('[data-cy="submit-order-btn"]').click()
+ cy.contains('提交成功').should('be.visible')
+
+ // 步骤1:护士校对并执行
+ cy.login('wx', '123456')
+ cy.visit('/inpatient/order-verify')
+ cy.get('[data-cy="tab-pending"]').click()
+ cy.get('[data-cy="order-checkbox-1"]').check()
+ cy.get('[data-cy="btn-verify"]').click()
+ cy.get('[data-cy="tab-executed"]').click()
+ cy.get('[data-cy="order-checkbox-1"]').check()
+ cy.get('[data-cy="btn-execute"]').click()
+ cy.contains('执行成功').should('be.visible')
+
+ // 步骤2:药房发药
+ cy.login('ykk1', '123456')
+ cy.visit('/pharmacy/dispense')
+ cy.get('[data-cy="dispense-list-item"]').first().click()
+ cy.get('[data-cy="btn-confirm-dispense"]').click()
+ cy.contains('发药成功').should('be.visible')
+
+ // 步骤3:护士尝试退回已发药医嘱
+ cy.login('wx', '123456')
+ cy.visit('/inpatient/order-verify')
+ cy.get('[data-cy="tab-executed"]').click()
+ cy.get('[data-cy="order-checkbox-1"]').check()
+
+ // 验证退回按钮交互:理想状态置灰,若未置灰则点击拦截
+ cy.get('[data-cy="btn-return"]').then($btn => {
+ if ($btn.is(':disabled')) {
+ cy.wrap($btn).should('be.disabled')
+ } else {
+ cy.wrap($btn).click({ force: true })
+ // 验证核心拦截提示
+ cy.contains('该药品已由药房发放,请先执行退药处理,不可直接退回').should('be.visible')
+ // 验证状态未发生流转(仍停留在已校对/已执行页签)
+ cy.get('[data-cy="tab-executed"]').should('have.class', 'is-active')
+ cy.get('[data-cy="tab-returned"]').should('not.have.class', 'is-active')
+ }
+ })
+ })
})