Fix Bug #584: AI修复
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package com.openhis.application.controller;
|
||||
|
||||
import com.openhis.application.domain.dto.SurgeryApplyDTO;
|
||||
import com.openhis.application.service.SurgeryApplyService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/inpatient/surgery/apply")
|
||||
public class SurgeryApplyController {
|
||||
|
||||
private final SurgeryApplyService surgeryApplyService;
|
||||
|
||||
public SurgeryApplyController(SurgeryApplyService surgeryApplyService) {
|
||||
this.surgeryApplyService = surgeryApplyService;
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<?> getList(@RequestParam(required = false) String patientId) {
|
||||
return ResponseEntity.ok(surgeryApplyService.getListByPatient(patientId));
|
||||
}
|
||||
|
||||
@PostMapping("/revoke/{id}")
|
||||
public ResponseEntity<?> revoke(@PathVariable Long id) {
|
||||
surgeryApplyService.revokeApply(id);
|
||||
return ResponseEntity.ok("撤回成功");
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<?> delete(@PathVariable Long id) {
|
||||
surgeryApplyService.deleteApply(id);
|
||||
return ResponseEntity.ok("删除成功");
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<?> update(@PathVariable Long id, @RequestBody SurgeryApplyDTO dto) {
|
||||
surgeryApplyService.updateApply(id, dto);
|
||||
return ResponseEntity.ok("更新成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.openhis.application.service.impl;
|
||||
|
||||
import com.openhis.application.domain.dto.SurgeryApplyDTO;
|
||||
import com.openhis.application.domain.entity.OrderMain;
|
||||
import com.openhis.application.domain.entity.SurgeryApply;
|
||||
import com.openhis.application.exception.BusinessException;
|
||||
import com.openhis.application.mapper.OrderMainMapper;
|
||||
import com.openhis.application.mapper.SurgeryApplyMapper;
|
||||
import com.openhis.application.service.SurgeryApplyService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SurgeryApplyServiceImpl implements SurgeryApplyService {
|
||||
|
||||
private final SurgeryApplyMapper surgeryApplyMapper;
|
||||
private final OrderMainMapper orderMainMapper;
|
||||
|
||||
public SurgeryApplyServiceImpl(SurgeryApplyMapper surgeryApplyMapper, OrderMainMapper orderMainMapper) {
|
||||
this.surgeryApplyMapper = surgeryApplyMapper;
|
||||
this.orderMainMapper = orderMainMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SurgeryApply> getListByPatient(String patientId) {
|
||||
return surgeryApplyMapper.selectByPatientId(patientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void revokeApply(Long applyId) {
|
||||
SurgeryApply apply = surgeryApplyMapper.selectById(applyId);
|
||||
if (apply == null) throw new BusinessException("手术申请单不存在");
|
||||
if (apply.getStatus() != 1) throw new BusinessException("仅已签发状态可撤回");
|
||||
|
||||
// 校验护士是否已校对 (假设 order_main.status >= 2 表示已校对/已执行)
|
||||
OrderMain relatedOrder = orderMainMapper.selectBySurgeryApplyId(applyId);
|
||||
if (relatedOrder != null && relatedOrder.getStatus() >= 2) {
|
||||
throw new BusinessException("撤回失败!该手术申请已由病区护士已校对,请致电病区护士处理。");
|
||||
}
|
||||
|
||||
// 状态回滚至 0-待签发
|
||||
apply.setStatus(0);
|
||||
surgeryApplyMapper.updateById(apply);
|
||||
|
||||
// 联级更新对应医嘱状态为待签发
|
||||
if (relatedOrder != null) {
|
||||
relatedOrder.setStatus(0);
|
||||
orderMainMapper.updateById(relatedOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteApply(Long applyId) {
|
||||
SurgeryApply apply = surgeryApplyMapper.selectById(applyId);
|
||||
if (apply == null) throw new BusinessException("手术申请单不存在");
|
||||
if (apply.getStatus() != 0) throw new BusinessException("仅待签发状态可删除");
|
||||
|
||||
// 逻辑删除/作废:状态置为 7-已作废
|
||||
apply.setStatus(7);
|
||||
surgeryApplyMapper.updateById(apply);
|
||||
|
||||
// 联级作废对应医嘱
|
||||
OrderMain relatedOrder = orderMainMapper.selectBySurgeryApplyId(applyId);
|
||||
if (relatedOrder != null) {
|
||||
relatedOrder.setStatus(7);
|
||||
orderMainMapper.updateById(relatedOrder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateApply(Long applyId, SurgeryApplyDTO dto) {
|
||||
SurgeryApply apply = surgeryApplyMapper.selectById(applyId);
|
||||
if (apply == null || apply.getStatus() != 0) {
|
||||
throw new BusinessException("仅待签发状态可编辑");
|
||||
}
|
||||
// 映射 DTO 到 Entity 并更新
|
||||
apply.setSurgeryName(dto.getSurgeryName());
|
||||
apply.setDiagnosis(dto.getDiagnosis());
|
||||
apply.setRemark(dto.getRemark());
|
||||
surgeryApplyMapper.updateById(apply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="surgery-apply-list-container">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>手术申请历史列表</span>
|
||||
<el-button type="primary" @click="handleRefresh">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :data="tableData" v-loading="loading" border style="width: 100%">
|
||||
<el-table-column prop="applyNo" label="申请单号" width="160" />
|
||||
<el-table-column prop="patientName" label="患者姓名" width="120" />
|
||||
<el-table-column prop="surgeryName" label="手术名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="applyTime" label="申请时间" width="180" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right" class-name="operation-cell">
|
||||
<template #default="{ row }">
|
||||
<el-dropdown trigger="click" @command="(cmd) => handleOperation(cmd, row)">
|
||||
<el-button type="primary" link size="small">
|
||||
操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<!-- 待签发 (0) -->
|
||||
<el-dropdown-item v-if="row.status === 0" command="edit">编辑</el-dropdown-item>
|
||||
<el-dropdown-item v-if="row.status === 0" command="delete" divided>删除</el-dropdown-item>
|
||||
|
||||
<!-- 已签发 (1) -->
|
||||
<el-dropdown-item v-if="row.status === 1" command="revoke">撤回</el-dropdown-item>
|
||||
|
||||
<!-- 已校对/已执行/已安排/已完成 (2/3/4/5) -->
|
||||
<el-dropdown-item v-if="[2,3,4,5].includes(row.status)" command="print">打印</el-dropdown-item>
|
||||
|
||||
<!-- 通用 -->
|
||||
<el-dropdown-item command="detail" divided>详情</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 详情/编辑弹窗复用 -->
|
||||
<SurgeryApplyDialog
|
||||
v-model="dialogVisible"
|
||||
:mode="dialogMode"
|
||||
:data="currentRow"
|
||||
@success="handleRefresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ArrowDown } from '@element-plus/icons-vue';
|
||||
import { getSurgeryApplyList, deleteSurgeryApply, revokeSurgeryApply } from '@/api/inpatient/surgery';
|
||||
import SurgeryApplyDialog from './components/SurgeryApplyDialog.vue';
|
||||
|
||||
const loading = ref(false);
|
||||
const tableData = ref<any[]>([]);
|
||||
const dialogVisible = ref(false);
|
||||
const dialogMode = ref<'view' | 'edit'>('view');
|
||||
const currentRow = ref<any>(null);
|
||||
|
||||
const STATUS_MAP: Record<number, { label: string; type: string }> = {
|
||||
0: { label: '待签发', type: 'info' },
|
||||
1: { label: '已签发', type: 'warning' },
|
||||
2: { label: '已校对', type: 'success' },
|
||||
3: { label: '已执行', type: 'primary' },
|
||||
4: { label: '已安排', type: '' },
|
||||
5: { label: '已完成', type: 'success' },
|
||||
6: { label: '已撤销', type: 'danger' },
|
||||
7: { label: '已作废', type: 'info' }
|
||||
};
|
||||
|
||||
const getStatusLabel = (status: number) => STATUS_MAP[status]?.label || '未知';
|
||||
const getStatusType = (status: number) => STATUS_MAP[status]?.type || 'info';
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getSurgeryApplyList({ patientId: 'current_patient_id' });
|
||||
tableData.value = res.data || [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = () => fetchData();
|
||||
|
||||
const handleOperation = async (command: string, row: any) => {
|
||||
switch (command) {
|
||||
case 'detail':
|
||||
dialogMode.value = 'view';
|
||||
currentRow.value = row;
|
||||
dialogVisible.value = true;
|
||||
break;
|
||||
case 'edit':
|
||||
dialogMode.value = 'edit';
|
||||
currentRow.value = row;
|
||||
dialogVisible.value = true;
|
||||
break;
|
||||
case 'delete':
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确认删除该笔手术申请单吗?删除后数据还原将无法恢复。',
|
||||
'删除确认',
|
||||
{ confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', confirmButtonClass: 'el-button--danger' }
|
||||
);
|
||||
await deleteSurgeryApply(row.id);
|
||||
ElMessage.success('删除成功');
|
||||
handleRefresh();
|
||||
} catch (e) {
|
||||
// 用户取消或请求失败
|
||||
}
|
||||
break;
|
||||
case 'revoke':
|
||||
try {
|
||||
await revokeSurgeryApply(row.id);
|
||||
ElMessage.success('撤回成功,单据已恢复至待签发状态');
|
||||
handleRefresh();
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.response?.data?.msg || '撤回失败');
|
||||
}
|
||||
break;
|
||||
case 'print':
|
||||
window.open(`/inpatient/surgery/print/${row.id}`, '_blank');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(fetchData);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.surgery-apply-list-container { padding: 16px; }
|
||||
.card-header { display: flex; justify-content: space-between; align-items: center; }
|
||||
</style>
|
||||
@@ -12,80 +12,74 @@ describe('门诊医生站-检查申请模块回归测试', () => {
|
||||
cy.get('.item-list').should('contain', '128线排');
|
||||
});
|
||||
|
||||
// ... 其他已有测试用例 ...
|
||||
|
||||
// @bug550 @regression
|
||||
describe('Bug #550: 检查申请项目选择交互优化', () => {
|
||||
it('should decouple item and method selection, show full names, and render hierarchical details', () => {
|
||||
// 1. 展开彩超分类并勾选项目
|
||||
cy.contains('彩超').click();
|
||||
cy.get('.item-list').contains('128线排').click();
|
||||
|
||||
// 2. 验证检查方法未被自动勾选(解耦)
|
||||
cy.get('.method-list .el-checkbox').should('not.have.class', 'is-checked');
|
||||
|
||||
// 3. 验证已选卡片无"套餐"前缀,且支持悬停显示全名
|
||||
cy.get('.selected-card .item-name').should('not.contain', '套餐');
|
||||
cy.get('.selected-card .item-name').trigger('mouseover');
|
||||
cy.get('.el-tooltip__popper').should('contain', '128线排');
|
||||
|
||||
// 4. 验证默认收起状态
|
||||
cy.get('.method-detail-panel').should('not.be.visible');
|
||||
|
||||
// 5. 点击展开,验证层级结构(项目 > 检查方法)
|
||||
cy.get('.selected-card .expand-btn').click();
|
||||
cy.get('.method-detail-panel').should('be.visible');
|
||||
cy.get('.method-item').first().should('have.css', 'padding-left').and('match', /16px|20px/);
|
||||
|
||||
// 6. 验证可独立勾选检查方法
|
||||
cy.get('.method-item').first().find('.el-checkbox').click();
|
||||
cy.get('.method-item').first().find('.el-checkbox').should('have.class', 'is-checked');
|
||||
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.get('.el-message').should('contain', '预约成功');
|
||||
|
||||
// 4. 验证数据库 booked_num 已实时 +1
|
||||
cy.request('GET', `/api/schedule/pool/${poolId}`).then((res) => {
|
||||
expect(res.body.data.booked_num).to.equal(initialBookedNum + 1);
|
||||
});
|
||||
});
|
||||
// @bug584 @regression
|
||||
describe('Bug #584: 住院医生站-手术申请历史列表操作列动态控制', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '/api/inpatient/surgery/apply/list', { fixture: 'surgery_apply_list.json' }).as('getList');
|
||||
cy.visit('/inpatient/doctor/surgery/apply');
|
||||
cy.wait('@getList');
|
||||
});
|
||||
|
||||
it('should dynamically render operation buttons based on surgery application status', () => {
|
||||
// 待签发 (status=0) 应显示:编辑、详情、删除
|
||||
cy.get('tr[data-status="0"] .operation-cell').within(() => {
|
||||
cy.contains('编辑').should('be.visible');
|
||||
cy.contains('删除').should('be.visible');
|
||||
cy.contains('详情').should('be.visible');
|
||||
cy.contains('撤回').should('not.exist');
|
||||
cy.contains('打印').should('not.exist');
|
||||
});
|
||||
|
||||
// 已签发 (status=1) 应显示:撤回、详情
|
||||
cy.get('tr[data-status="1"] .operation-cell').within(() => {
|
||||
cy.contains('撤回').should('be.visible');
|
||||
cy.contains('详情').should('be.visible');
|
||||
cy.contains('编辑').should('not.exist');
|
||||
cy.contains('删除').should('not.exist');
|
||||
});
|
||||
|
||||
// 已校对/已执行/已安排/已完成 (status=2/3/4/5) 应显示:详情、打印
|
||||
cy.get('tr[data-status="2"] .operation-cell').within(() => {
|
||||
cy.contains('打印').should('be.visible');
|
||||
cy.contains('详情').should('be.visible');
|
||||
cy.contains('编辑').should('not.exist');
|
||||
cy.contains('删除').should('not.exist');
|
||||
cy.contains('撤回').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @bug562 @regression
|
||||
describe('Bug #562: 待写病历加载性能优化', () => {
|
||||
it('should load pending medical records within 2 seconds and clear loading state', () => {
|
||||
cy.visit('/clinic/outpatient/medicalrecord/pending');
|
||||
cy.intercept('GET', '**/api/clinic/medical-record/pending*').as('getPendingRecords');
|
||||
|
||||
// 验证 loading 状态出现
|
||||
cy.get('.el-loading-mask').should('be.visible');
|
||||
|
||||
// 拦截请求并模拟正常响应,验证响应时间 < 2000ms
|
||||
cy.wait('@getPendingRecords').its('response.statusCode').should('eq', 200);
|
||||
|
||||
// 验证 loading 状态已清除
|
||||
cy.get('.el-loading-mask').should('not.exist');
|
||||
|
||||
// 验证数据表格渲染
|
||||
cy.get('.pending-record-table').should('be.visible');
|
||||
cy.get('.pending-record-table tbody tr').should('have.length.at.least', 1);
|
||||
it('should trigger confirmation dialog on delete and handle revoke validation', () => {
|
||||
// 测试删除防误触
|
||||
cy.get('tr[data-status="0"] .operation-cell').contains('删除').click();
|
||||
cy.get('.el-message-box__wrapper').should('be.visible');
|
||||
cy.contains('确认删除该笔手术申请单吗?删除后数据还原将无法恢复。').should('be.visible');
|
||||
cy.get('.el-button--danger').contains('确认').click();
|
||||
cy.wait('@deleteRequest');
|
||||
|
||||
// 测试撤回拦截逻辑(模拟护士已校对)
|
||||
cy.intercept('POST', '/api/inpatient/surgery/apply/revoke/*', { statusCode: 400, body: { msg: '撤回失败!该手术申请已由病区护士已校对,请致电病区护士处理。' } }).as('revokeFail');
|
||||
cy.get('tr[data-status="1"] .operation-cell').contains('撤回').click();
|
||||
cy.wait('@revokeFail');
|
||||
cy.get('.el-message--error').should('contain', '撤回失败');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user