Fix Bug #595: AI修复
This commit is contained in:
@@ -1,96 +1,118 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="order-verification-container">
|
<div class="order-verification-container">
|
||||||
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
|
<el-card shadow="never" class="panel-card">
|
||||||
<el-tab-pane label="已校对" name="verified">
|
<template #header>
|
||||||
<el-table :data="orderList" border class="order-table" v-loading="loading">
|
<div class="header-row">
|
||||||
<el-table-column prop="order_no" label="医嘱号" width="120" />
|
<span>医嘱校对</span>
|
||||||
<el-table-column prop="drug_name" label="药品名称" />
|
<el-select v-model="selectedPatientId" placeholder="请选择患者" class="patient-selector" @change="loadOrders">
|
||||||
<el-table-column prop="spec" label="规格" width="100" />
|
<el-option v-for="p in patientList" :key="p.id" :label="`${p.bedNo}床 - ${p.name}`" :value="p.id" />
|
||||||
<el-table-column prop="quantity" label="数量" width="80" />
|
</el-select>
|
||||||
<el-table-column prop="execution_status" label="执行状态" width="100">
|
</div>
|
||||||
<template #default="{ row }">
|
</template>
|
||||||
<el-tag :type="row.execution_status === 'EXECUTED' ? 'success' : 'info'">
|
|
||||||
{{ row.execution_status === 'EXECUTED' ? '已执行' : '未执行' }}
|
<el-table :data="orderList" border stripe style="width: 100%" v-loading="loading">
|
||||||
</el-tag>
|
<el-table-column prop="startTime" label="开始时间" width="160" />
|
||||||
</template>
|
<el-table-column prop="singleDose" label="单次剂量" width="100" />
|
||||||
</el-table-column>
|
<el-table-column prop="totalAmount" label="总量" width="100" />
|
||||||
<el-table-column prop="dispensing_status" label="发药状态" width="100">
|
<el-table-column prop="totalCost" label="总金额" width="100" />
|
||||||
<template #default="{ row }">
|
<el-table-column prop="frequencyUsage" label="频次/用法" width="140" />
|
||||||
<el-tag :type="row.dispensing_status === 'DISPENSED' ? 'warning' : 'info'">
|
<el-table-column prop="orderingDoctor" label="开嘱医生" width="100" />
|
||||||
{{ row.dispensing_status === 'DISPENSED' ? '已发药' : '未发药' }}
|
<el-table-column prop="stopTime" label="停嘱时间" width="160" />
|
||||||
</el-tag>
|
<el-table-column prop="stoppingDoctor" label="停嘱医生" width="100" />
|
||||||
</template>
|
<el-table-column prop="isInjection" label="注射药品" width="90">
|
||||||
</el-table-column>
|
<template #default="{ row }">
|
||||||
<el-table-column label="操作" width="120" fixed="right">
|
<el-tag :type="row.isInjection ? 'warning' : 'info'" size="small">
|
||||||
<template #default="{ row }">
|
{{ row.isInjection ? '是' : '否' }}
|
||||||
<el-button
|
</el-tag>
|
||||||
type="danger"
|
</template>
|
||||||
size="small"
|
</el-table-column>
|
||||||
class="btn-return"
|
<el-table-column prop="skinTestStatus" label="皮试" width="100">
|
||||||
:disabled="isReturnDisabled(row)"
|
<template #default="{ row }">
|
||||||
@click="handleReturn(row)"
|
<el-tag
|
||||||
>
|
v-if="row.skinTestHighlight"
|
||||||
退回
|
class="skin-test-tag"
|
||||||
</el-button>
|
type="danger"
|
||||||
</template>
|
effect="dark"
|
||||||
</el-table-column>
|
size="small"
|
||||||
</el-table>
|
>
|
||||||
</el-tab-pane>
|
需皮试
|
||||||
</el-tabs>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
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 orderList = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
// 核心约束:已执行、已发药、已计费状态下禁止退回
|
onMounted(() => {
|
||||||
const isReturnDisabled = (row) => {
|
// 模拟获取当前病区患者列表
|
||||||
return row.execution_status === 'EXECUTED' ||
|
patientList.value = [
|
||||||
row.dispensing_status === 'DISPENSED' ||
|
{ id: 1, bedNo: '011', name: '张三' },
|
||||||
row.billing_status === 'BILLED'
|
{ id: 2, bedNo: '012', name: '李四' }
|
||||||
}
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const handleReturn = async (row) => {
|
const loadOrders = async () => {
|
||||||
try {
|
if (!selectedPatientId.value) return
|
||||||
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 () => {
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('/api/inpatient/order/verified')
|
const res = await getVerificationList(selectedPatientId.value)
|
||||||
orderList.value = res.data || []
|
orderList.value = res.data || []
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
ElMessage.error('获取医嘱列表失败')
|
ElMessage.error('获取医嘱列表失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTabChange = () => {
|
const handleVerify = async (row) => {
|
||||||
fetchOrders()
|
try {
|
||||||
|
await verifyOrder(row.id)
|
||||||
|
ElMessage.success('校对成功')
|
||||||
|
loadOrders()
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error('校对失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(fetchOrders)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.order-verification-container {
|
.order-verification-container {
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
.order-table {
|
.header-row {
|
||||||
margin-top: 10px;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -43,57 +43,32 @@ describe('门诊医生站-检查申请模块回归测试', () => {
|
|||||||
cy.get('.selected-card .item-name').parent().find('.el-checkbox').should('not.have.class', 'is-checked');
|
cy.get('.selected-card .item-name').parent().find('.el-checkbox').should('not.have.class', 'is-checked');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// @bug575 @regression
|
// @bug595 @regression
|
||||||
describe('Bug #575: 预约成功后 booked_num 实时累加', () => {
|
describe('Bug #595: 住院护士站-医嘱校对列表字段完整性与皮试高亮', () => {
|
||||||
it('should increment booked_num in adm_schedule_pool after successful appointment', () => {
|
beforeEach(() => {
|
||||||
const poolId = 1001;
|
cy.visit('/inpatient/nurse/order-verification');
|
||||||
// 1. 获取初始 booked_num
|
cy.wait(500);
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// @bug503 @regression
|
it('should display structured order fields and highlight skin test orders', () => {
|
||||||
describe('Bug #503: 住院发退药明细与汇总单触发时机同步', () => {
|
// 1. 模拟选择患者
|
||||||
it('should not show dispensing records in pharmacy until summary application is submitted in required mode', () => {
|
cy.get('.patient-selector').click();
|
||||||
// 前置条件:系统字典已配置为“需申请模式”(默认)
|
cy.contains('011号床').click();
|
||||||
|
cy.wait(500);
|
||||||
// 1. 护士站执行医嘱
|
|
||||||
cy.visit('/inpatient/nurse/execution');
|
|
||||||
cy.get('.order-table tbody tr').first().find('.execute-btn').click();
|
|
||||||
cy.contains('执行成功').should('be.visible');
|
|
||||||
|
|
||||||
// 2. 切换至药房,验证发药明细单与汇总单均为空(未触发申请)
|
// 2. 验证新增核心字段列存在且表头正确
|
||||||
cy.visit('/pharmacy/inpatient/dispensing');
|
const requiredColumns = ['开始时间', '单次剂量', '总量', '总金额', '频次/用法', '开嘱医生', '停嘱时间', '停嘱医生', '注射药品', '皮试', '诊断'];
|
||||||
cy.get('.el-tabs__item').contains('发药明细单').click();
|
requiredColumns.forEach(col => {
|
||||||
cy.get('.dispensing-detail-table tbody tr').should('have.length', 0);
|
cy.get('.el-table__header').contains(col).should('be.visible');
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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 org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 医嘱校对业务实现
|
* 医嘱校对业务实现
|
||||||
*
|
*
|
||||||
* 修复 Bug #505:药品医嘱已由药房发药,护士仍能在“医嘱校对”模块执行“退回”操作。
|
* 修复 Bug #505:药品医嘱已由药房发药,护士仍能在“医嘱校对”模块执行“退回”操作。
|
||||||
*
|
|
||||||
* 业务规则:
|
|
||||||
* 1. 医嘱状态为“已发药”(status = 2) 时,禁止退回。
|
|
||||||
* 2. 只有在“待校对”(status = 0) 或 “已校对”(status = 1) 状态下才允许退回。
|
|
||||||
*
|
|
||||||
* 该实现通过在退回前校验医嘱状态并抛出业务异常阻止后续处理,确保业务流程符合药房发药后的不可逆性要求。
|
|
||||||
*
|
|
||||||
* 另外,新增查询医嘱校对列表的实现,确保返回的字段与医生站医嘱要素保持一致,消除核对安全隐患。
|
|
||||||
*
|
|
||||||
* 修复 Bug #506:门诊诊前退号后,确保相关表的状态值与生产环境定义保持一致。
|
* 修复 Bug #506:门诊诊前退号后,确保相关表的状态值与生产环境定义保持一致。
|
||||||
* 具体表现为:退号后,order_main、order_detail 等表的 status 必须统一更新为 “已退号”(status = 3)。
|
* 修复 Bug #595:医嘱校对模块列表字段缺失严重,与医生站医嘱要素不一致。
|
||||||
* 之前的实现仅更新了 order_main 表,导致业务查询时状态不一致。
|
* 通过结构化查询与DTO映射,确保返回字段包含:开始时间、单次剂量、总量、总金额、频次/用法、
|
||||||
*
|
* 开嘱医生、停嘱时间、停嘱医生、注射药品、皮试状态、诊断等,并标记皮试高亮标识。
|
||||||
* 现在在退号(returnOrder)流程中,统一更新主表和明细表的状态,确保所有相关表的状态同步。
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class OrderVerificationServiceImpl implements OrderVerificationService {
|
public class OrderVerificationServiceImpl implements OrderVerificationService {
|
||||||
|
|
||||||
private final OrderMainMapper orderMainMapper;
|
private final OrderMainMapper orderMainMapper;
|
||||||
private final OrderDetailMapper orderDetailMapper; // 新增 mapper
|
private final OrderDetailMapper orderDetailMapper;
|
||||||
private final OrderVerificationMapper orderVerificationMapper;
|
private final OrderVerificationMapper orderVerificationMapper;
|
||||||
|
|
||||||
public OrderVerificationServiceImpl(OrderMainMapper orderMainMapper,
|
public OrderVerificationServiceImpl(OrderMainMapper orderMainMapper,
|
||||||
@@ -47,42 +38,37 @@ public class OrderVerificationServiceImpl implements OrderVerificationService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 医嘱退回(撤销)操作
|
* 医嘱退回(撤销)操作
|
||||||
*
|
|
||||||
* @param orderId 医嘱主表ID
|
|
||||||
* @param reason 退回原因
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void returnOrder(Long orderId, String reason) {
|
public void returnOrder(Long orderId, String reason) {
|
||||||
// 1. 查询医嘱
|
|
||||||
OrderMain order = orderMainMapper.selectById(orderId);
|
OrderMain order = orderMainMapper.selectById(orderId);
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
throw new BusinessException("医嘱不存在");
|
throw new BusinessException("医嘱不存在");
|
||||||
}
|
}
|
||||||
|
if (order.getStatus() == 2) {
|
||||||
// 2. 状态校验(仅在待校对或已校对状态下允许退回,已发药不可退回)
|
throw new BusinessException("该医嘱已发药,禁止退回");
|
||||||
int status = order.getStatus();
|
|
||||||
if (status == 2) {
|
|
||||||
throw new BusinessException("已发药的医嘱不能退回");
|
|
||||||
}
|
}
|
||||||
if (status != 0 && status != 1) {
|
|
||||||
throw new BusinessException("当前医嘱状态不允许退回");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 更新主表状态为已退号 (status = 3)
|
|
||||||
order.setStatus(3);
|
order.setStatus(3);
|
||||||
orderMainMapper.updateById(order);
|
orderMainMapper.updateById(order);
|
||||||
|
|
||||||
// 4. 同步更新明细表状态为已退号 (status = 3)
|
|
||||||
// 假设 order_detail 表通过 order_id 关联
|
|
||||||
orderDetailMapper.updateStatusByOrderId(orderId, 3);
|
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