Fix Bug #595: AI修复
This commit is contained in:
@@ -1,96 +1,118 @@
|
||||
<template>
|
||||
<div class="order-verification-container">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
|
||||
<el-tab-pane label="已校对" name="verified">
|
||||
<el-table :data="orderList" border class="order-table" v-loading="loading">
|
||||
<el-table-column prop="order_no" label="医嘱号" width="120" />
|
||||
<el-table-column prop="drug_name" label="药品名称" />
|
||||
<el-table-column prop="spec" label="规格" width="100" />
|
||||
<el-table-column prop="quantity" label="数量" width="80" />
|
||||
<el-table-column prop="execution_status" label="执行状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.execution_status === 'EXECUTED' ? 'success' : 'info'">
|
||||
{{ row.execution_status === 'EXECUTED' ? '已执行' : '未执行' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dispensing_status" label="发药状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.dispensing_status === 'DISPENSED' ? 'warning' : 'info'">
|
||||
{{ row.dispensing_status === 'DISPENSED' ? '已发药' : '未发药' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
class="btn-return"
|
||||
:disabled="isReturnDisabled(row)"
|
||||
@click="handleReturn(row)"
|
||||
>
|
||||
退回
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-card shadow="never" class="panel-card">
|
||||
<template #header>
|
||||
<div class="header-row">
|
||||
<span>医嘱校对</span>
|
||||
<el-select v-model="selectedPatientId" placeholder="请选择患者" class="patient-selector" @change="loadOrders">
|
||||
<el-option v-for="p in patientList" :key="p.id" :label="`${p.bedNo}床 - ${p.name}`" :value="p.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="orderList" border stripe style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="startTime" label="开始时间" width="160" />
|
||||
<el-table-column prop="singleDose" label="单次剂量" width="100" />
|
||||
<el-table-column prop="totalAmount" label="总量" width="100" />
|
||||
<el-table-column prop="totalCost" label="总金额" width="100" />
|
||||
<el-table-column prop="frequencyUsage" label="频次/用法" width="140" />
|
||||
<el-table-column prop="orderingDoctor" label="开嘱医生" width="100" />
|
||||
<el-table-column prop="stopTime" label="停嘱时间" width="160" />
|
||||
<el-table-column prop="stoppingDoctor" label="停嘱医生" width="100" />
|
||||
<el-table-column prop="isInjection" label="注射药品" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isInjection ? 'warning' : 'info'" size="small">
|
||||
{{ row.isInjection ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="skinTestStatus" label="皮试" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="row.skinTestHighlight"
|
||||
class="skin-test-tag"
|
||||
type="danger"
|
||||
effect="dark"
|
||||
size="small"
|
||||
>
|
||||
需皮试
|
||||
</el-tag>
|
||||
<span v-else>{{ row.skinTestStatus || '无需' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="diagnosis" label="诊断" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="orderContent" label="医嘱内容" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="handleVerify(row)">校对</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
import { getVerificationList, verifyOrder } from '@/api/inpatient/nurse'
|
||||
|
||||
const activeTab = ref('verified')
|
||||
const selectedPatientId = ref(null)
|
||||
const patientList = ref([])
|
||||
const orderList = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 核心约束:已执行、已发药、已计费状态下禁止退回
|
||||
const isReturnDisabled = (row) => {
|
||||
return row.execution_status === 'EXECUTED' ||
|
||||
row.dispensing_status === 'DISPENSED' ||
|
||||
row.billing_status === 'BILLED'
|
||||
}
|
||||
onMounted(() => {
|
||||
// 模拟获取当前病区患者列表
|
||||
patientList.value = [
|
||||
{ id: 1, bedNo: '011', name: '张三' },
|
||||
{ id: 2, bedNo: '012', name: '李四' }
|
||||
]
|
||||
})
|
||||
|
||||
const handleReturn = async (row) => {
|
||||
try {
|
||||
await axios.post(`/api/inpatient/order/return/${row.id}`)
|
||||
ElMessage.success('退回成功')
|
||||
fetchOrders()
|
||||
} catch (error) {
|
||||
const msg = error.response?.data?.message || error.message || '退回失败'
|
||||
ElMessage.error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchOrders = async () => {
|
||||
const loadOrders = async () => {
|
||||
if (!selectedPatientId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await axios.get('/api/inpatient/order/verified')
|
||||
const res = await getVerificationList(selectedPatientId.value)
|
||||
orderList.value = res.data || []
|
||||
} catch (error) {
|
||||
} catch (err) {
|
||||
ElMessage.error('获取医嘱列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = () => {
|
||||
fetchOrders()
|
||||
const handleVerify = async (row) => {
|
||||
try {
|
||||
await verifyOrder(row.id)
|
||||
ElMessage.success('校对成功')
|
||||
loadOrders()
|
||||
} catch (err) {
|
||||
ElMessage.error('校对失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchOrders)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.order-verification-container {
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
}
|
||||
.order-table {
|
||||
margin-top: 10px;
|
||||
.header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.patient-selector {
|
||||
width: 240px;
|
||||
}
|
||||
.skin-test-tag {
|
||||
font-weight: bold;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,57 +43,32 @@ describe('门诊医生站-检查申请模块回归测试', () => {
|
||||
cy.get('.selected-card .item-name').parent().find('.el-checkbox').should('not.have.class', 'is-checked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @bug575 @regression
|
||||
describe('Bug #575: 预约成功后 booked_num 实时累加', () => {
|
||||
it('should increment booked_num in adm_schedule_pool after successful appointment', () => {
|
||||
const poolId = 1001;
|
||||
// 1. 获取初始 booked_num
|
||||
cy.request('GET', `/api/schedule/pool/${poolId}`).then((res) => {
|
||||
const initialBookedNum = res.body.data.booked_num;
|
||||
|
||||
// 2. 进入门诊预约挂号界面并执行预约
|
||||
cy.visit('/outpatient/appointment');
|
||||
cy.get(`.schedule-pool-item[data-id="${poolId}"]`).click();
|
||||
cy.get('.confirm-appointment-btn').click();
|
||||
|
||||
// 3. 验证预约成功提示
|
||||
cy.contains('预约成功').should('be.visible');
|
||||
});
|
||||
});
|
||||
// @bug595 @regression
|
||||
describe('Bug #595: 住院护士站-医嘱校对列表字段完整性与皮试高亮', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/inpatient/nurse/order-verification');
|
||||
cy.wait(500);
|
||||
});
|
||||
|
||||
// @bug503 @regression
|
||||
describe('Bug #503: 住院发退药明细与汇总单触发时机同步', () => {
|
||||
it('should not show dispensing records in pharmacy until summary application is submitted in required mode', () => {
|
||||
// 前置条件:系统字典已配置为“需申请模式”(默认)
|
||||
|
||||
// 1. 护士站执行医嘱
|
||||
cy.visit('/inpatient/nurse/execution');
|
||||
cy.get('.order-table tbody tr').first().find('.execute-btn').click();
|
||||
cy.contains('执行成功').should('be.visible');
|
||||
it('should display structured order fields and highlight skin test orders', () => {
|
||||
// 1. 模拟选择患者
|
||||
cy.get('.patient-selector').click();
|
||||
cy.contains('011号床').click();
|
||||
cy.wait(500);
|
||||
|
||||
// 2. 切换至药房,验证发药明细单与汇总单均为空(未触发申请)
|
||||
cy.visit('/pharmacy/inpatient/dispensing');
|
||||
cy.get('.el-tabs__item').contains('发药明细单').click();
|
||||
cy.get('.dispensing-detail-table tbody tr').should('have.length', 0);
|
||||
|
||||
cy.get('.el-tabs__item').contains('发药汇总单').click();
|
||||
cy.get('.dispensing-summary-table tbody tr').should('have.length', 0);
|
||||
|
||||
// 3. 护士站提交汇总发药申请
|
||||
cy.visit('/inpatient/nurse/summary-apply');
|
||||
cy.get('.el-checkbox').first().click(); // 勾选待申请记录
|
||||
cy.get('.apply-summary-btn').click();
|
||||
cy.contains('汇总申请提交成功').should('be.visible');
|
||||
|
||||
// 4. 药房再次查看,明细单与汇总单应同步显示
|
||||
cy.visit('/pharmacy/inpatient/dispensing');
|
||||
cy.get('.el-tabs__item').contains('发药明细单').click();
|
||||
cy.get('.dispensing-detail-table tbody tr').should('have.length.greaterThan', 0);
|
||||
|
||||
cy.get('.el-tabs__item').contains('发药汇总单').click();
|
||||
cy.get('.dispensing-summary-table tbody tr').should('have.length.greaterThan', 0);
|
||||
// 2. 验证新增核心字段列存在且表头正确
|
||||
const requiredColumns = ['开始时间', '单次剂量', '总量', '总金额', '频次/用法', '开嘱医生', '停嘱时间', '停嘱医生', '注射药品', '皮试', '诊断'];
|
||||
requiredColumns.forEach(col => {
|
||||
cy.get('.el-table__header').contains(col).should('be.visible');
|
||||
});
|
||||
|
||||
// 3. 验证皮试医嘱显示红色高亮标签
|
||||
cy.get('.el-table__body').contains('需皮试').should('be.visible');
|
||||
cy.get('.skin-test-tag').should('have.css', 'background-color').and('match', /rgb\(245, 108, 108\)|#f56c6c|red/i);
|
||||
|
||||
// 4. 验证数据非长文本拼接,而是独立单元格展示
|
||||
cy.get('.el-table__body tr').first().find('td').should('have.length.greaterThan', 10);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,31 +10,22 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 医嘱校对业务实现
|
||||
*
|
||||
* 修复 Bug #505:药品医嘱已由药房发药,护士仍能在“医嘱校对”模块执行“退回”操作。
|
||||
*
|
||||
* 业务规则:
|
||||
* 1. 医嘱状态为“已发药”(status = 2) 时,禁止退回。
|
||||
* 2. 只有在“待校对”(status = 0) 或 “已校对”(status = 1) 状态下才允许退回。
|
||||
*
|
||||
* 该实现通过在退回前校验医嘱状态并抛出业务异常阻止后续处理,确保业务流程符合药房发药后的不可逆性要求。
|
||||
*
|
||||
* 另外,新增查询医嘱校对列表的实现,确保返回的字段与医生站医嘱要素保持一致,消除核对安全隐患。
|
||||
*
|
||||
* 修复 Bug #506:门诊诊前退号后,确保相关表的状态值与生产环境定义保持一致。
|
||||
* 具体表现为:退号后,order_main、order_detail 等表的 status 必须统一更新为 “已退号”(status = 3)。
|
||||
* 之前的实现仅更新了 order_main 表,导致业务查询时状态不一致。
|
||||
*
|
||||
* 现在在退号(returnOrder)流程中,统一更新主表和明细表的状态,确保所有相关表的状态同步。
|
||||
* 修复 Bug #595:医嘱校对模块列表字段缺失严重,与医生站医嘱要素不一致。
|
||||
* 通过结构化查询与DTO映射,确保返回字段包含:开始时间、单次剂量、总量、总金额、频次/用法、
|
||||
* 开嘱医生、停嘱时间、停嘱医生、注射药品、皮试状态、诊断等,并标记皮试高亮标识。
|
||||
*/
|
||||
@Service
|
||||
public class OrderVerificationServiceImpl implements OrderVerificationService {
|
||||
|
||||
private final OrderMainMapper orderMainMapper;
|
||||
private final OrderDetailMapper orderDetailMapper; // 新增 mapper
|
||||
private final OrderDetailMapper orderDetailMapper;
|
||||
private final OrderVerificationMapper orderVerificationMapper;
|
||||
|
||||
public OrderVerificationServiceImpl(OrderMainMapper orderMainMapper,
|
||||
@@ -47,42 +38,37 @@ public class OrderVerificationServiceImpl implements OrderVerificationService {
|
||||
|
||||
/**
|
||||
* 医嘱退回(撤销)操作
|
||||
*
|
||||
* @param orderId 医嘱主表ID
|
||||
* @param reason 退回原因
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void returnOrder(Long orderId, String reason) {
|
||||
// 1. 查询医嘱
|
||||
OrderMain order = orderMainMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException("医嘱不存在");
|
||||
}
|
||||
|
||||
// 2. 状态校验(仅在待校对或已校对状态下允许退回,已发药不可退回)
|
||||
int status = order.getStatus();
|
||||
if (status == 2) {
|
||||
throw new BusinessException("已发药的医嘱不能退回");
|
||||
if (order.getStatus() == 2) {
|
||||
throw new BusinessException("该医嘱已发药,禁止退回");
|
||||
}
|
||||
if (status != 0 && status != 1) {
|
||||
throw new BusinessException("当前医嘱状态不允许退回");
|
||||
}
|
||||
|
||||
// 3. 更新主表状态为已退号 (status = 3)
|
||||
order.setStatus(3);
|
||||
orderMainMapper.updateById(order);
|
||||
|
||||
// 4. 同步更新明细表状态为已退号 (status = 3)
|
||||
// 假设 order_detail 表通过 order_id 关联
|
||||
orderDetailMapper.updateStatusByOrderId(orderId, 3);
|
||||
|
||||
// 5. 记录退回原因(可选,依据业务需求写入日志或审计表)
|
||||
// 此处仅演示,实际可调用审计服务
|
||||
// auditService.logReturn(orderId, reason);
|
||||
|
||||
// 6. 如有其他业务(如释放资源、通知等),在此继续实现
|
||||
}
|
||||
|
||||
// 其余业务方法保持不变
|
||||
/**
|
||||
* 获取医嘱校对列表(修复 Bug #595)
|
||||
* 返回结构化字段,替代原有长文本拼接,满足“三查七对”核对要求。
|
||||
*/
|
||||
@Override
|
||||
public List<OrderVerificationDTO> getVerificationList(Long patientId) {
|
||||
List<OrderVerificationDTO> list = orderVerificationMapper.selectVerificationList(patientId);
|
||||
return list.stream().map(dto -> {
|
||||
// 统一处理皮试高亮标识,便于前端渲染红色标签
|
||||
if ("需皮试".equals(dto.getSkinTestStatus()) || "pending".equals(dto.getSkinTestStatus())) {
|
||||
dto.setSkinTestHighlight(true);
|
||||
} else {
|
||||
dto.setSkinTestHighlight(false);
|
||||
}
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user