Compare commits

...

5 Commits

Author SHA1 Message Date
关羽
65203d466d Fix Bug #401: 门诊完诊审计日志错误:div_log 表中 pool_id 与 slot_id 存值与设计规范不符
根因:queueWasAlreadyCompleted 条件限制导致队列已由分诊台完诊时,
医生站完诊不写 div_log 审计日志,造成审计记录缺失。
数据库12条COMPLETE记录中6条pool_id/slot_id为NULL(50%)。

修复:移除 queueWasAlreadyCompleted 条件限制,确保每次完诊操作
都生成审计日志;保留 queueWasAlreadyCompleted 日志用于排查。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 12:10:22 +08:00
c007ee3458 bug524 [门诊/医生个人报卡管理] 传染病报告卡保存后数据回显失败:病例分类、日期及分型字段为空 2026-05-16 12:10:22 +08:00
9c0302b920 bug432 门诊手术安排:新增手术安排保存时报错 2026-05-16 12:10:22 +08:00
wangjian963
1a5c65bbf7 Fix Bug 505505 【业务逻辑缺陷】药品医嘱已由药房发药,护士仍能在“医嘱校对”模块执行“退回”操作
[门诊手术安排]“手术申请查询”弹窗底部,分页组件与界底部元素重叠,影响操作。
2026-05-16 12:10:22 +08:00
关羽
95f9bc1698 Fix Bug #536: [门诊手术安排]"手术申请查询"弹窗底部,分页组件与界面底部元素重叠,影响操作。
根因:弹窗 body 无高度约束,窗口缩小时内容溢出导致分页与 footer 重叠。
修复:为弹窗添加 max-height: 75vh + overflow-y: auto 约束,分页与 footer 增加间距和分隔线。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 12:10:22 +08:00
7 changed files with 109 additions and 60 deletions

View File

@@ -83,6 +83,9 @@ public class InfectiousCardDto {
/** 病例分类 */
private String diseaseType;
/** 病例分类 */
private Integer caseClass;
/** 发病日期 */
private LocalDate onsetDate;

View File

@@ -345,23 +345,25 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
encounterId, tenantId);
}
// 写入 div_log 审计日志(独立于队列项,确保每次完诊都生成记录)
// Bug #401使用更新前记录的原始状态判断,避免自身更新后将状态改为 COMPLETED 导致误判为"已完成"
if (!queueWasAlreadyCompleted) {
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
DivLog divLog = new DivLog()
.setPoolId(divPoolId)
.setSlotId(divSlotId)
.setOpUserId(loginUser != null ? loginUser.getUserId() : null)
.setAction("COMPLETE")
.setCreateTime(LocalDateTime.now())
.setUpdateAt(LocalDateTime.now())
.setCreatedAt(LocalDateTime.now());
divLogService.save(divLog);
} catch (Exception e) {
log.error("写入div_log审计日志失败", e);
// 写入 div_log 审计日志(每次完诊都生成记录,确保审计链路完整
// Bug #401移除 queueWasAlreadyCompleted 条件限制,避免队列已由分诊台完诊时
// 医生站完诊不写日志导致审计记录缺失;同时保留 queueWasAlreadyCompleted 日志用于排查
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
DivLog divLog = new DivLog()
.setPoolId(divPoolId)
.setSlotId(divSlotId)
.setOpUserId(loginUser != null ? loginUser.getUserId() : null)
.setAction("COMPLETE")
.setCreateTime(LocalDateTime.now())
.setUpdateAt(LocalDateTime.now())
.setCreatedAt(LocalDateTime.now());
divLogService.save(divLog);
if (queueWasAlreadyCompleted) {
log.info("完诊:队列项已由分诊台完诊,医生站补充写入审计日志 encounterId={}", encounterId);
}
} catch (Exception e) {
log.error("写入div_log审计日志失败", e);
}
// 4. 更新状态、完成时间以及初复诊标识

View File

@@ -112,12 +112,15 @@ public class InfectiousDiseaseReportDto {
private Integer caseClass;
/** 发病日期(默认诊断时间,病原携带者填初检日期) */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate onsetDate;
/** 诊断日期(精确到小时) */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime diagDate;
/** 死亡日期(死亡病例必填) */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate deathDate;
/** 订正病名(订正报告必填) */
@@ -136,6 +139,7 @@ public class InfectiousDiseaseReportDto {
private String reportDoc;
/** 填卡日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate reportDate;
/** 报卡名称代码 1-中华人民共和国传染病报告卡 */
@@ -160,4 +164,4 @@ public class InfectiousDiseaseReportDto {
/** 医生ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long doctorId;
}
}

View File

@@ -178,9 +178,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
inpatientAdviceParam.setEncounterIds(null);
Integer exeStatus = inpatientAdviceParam.getExeStatus();
inpatientAdviceParam.setExeStatus(null);
// requestStatus由前端tab控制,需在后端过滤
Integer requestStatus = inpatientAdviceParam.getRequestStatus();
inpatientAdviceParam.setRequestStatus(null);
// requestStatus由前端tab传入通过QueryWrapper自动添加到SQL外层WHERE过滤
// 构建查询条件
QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
@@ -293,16 +291,6 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
inpatientAdvicePage.setTotal(filteredList.size());
}
}
// 按请求状态tab切换过滤医嘱
if (requestStatus != null) {
List<InpatientAdviceDto> statusFilteredList = inpatientAdvicePage.getRecords().stream()
.filter(advice -> requestStatus.equals(advice.getRequestStatus()))
.collect(Collectors.toList());
inpatientAdvicePage.setRecords(statusFilteredList);
inpatientAdvicePage.setTotal(statusFilteredList.size());
}
return R.ok(inpatientAdvicePage);
}
@@ -379,7 +367,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
.in(MedicationDispense::getMedReqId, medReqIds)
.eq(MedicationDispense::getStatusEnum, DispenseStatus.COMPLETED.getValue()));
if (!dispenseList.isEmpty()) {
return R.fail("医嘱已发药,无法退回");
return R.fail("药品已由药房发放,请先执行退药处理,不可直接退回");
}
}
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();

View File

@@ -1025,7 +1025,11 @@ function parseBirthDate(birthDate) {
function normalizeDate(value) {
if (!value) return '';
return String(value).split(/[T ]/)[0];
const datePart = String(value).split(/[T ]/)[0].replace(/\//g, '-');
const parts = datePart.split('-');
if (parts.length !== 3) return datePart;
const [year, month, day] = parts;
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
}
function normalizeSex(value) {
@@ -1116,7 +1120,7 @@ function showReport(reportData = {}, readOnly = true) {
addressHouse: reportData.addressHouse || '',
patientBelong: reportData.patientBelong || 1,
occupation: reportData.occupation || '',
caseClass: reportData.diseaseType != null ? String(reportData.diseaseType) : '',
caseClass: reportData.caseClass != null ? String(reportData.caseClass) : '',
onsetDate: normalizeDate(reportData.onsetDate),
diagDate: normalizeDate(reportData.diagDate),
deathDate: normalizeDate(reportData.deathDate),
@@ -1125,7 +1129,7 @@ function showReport(reportData = {}, readOnly = true) {
selectedClassB: diseaseSelection.selectedClassB,
selectedClassC: diseaseSelection.selectedClassC,
otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''),
diseaseType: reportData.diseaseSubtype || '',
diseaseType: reportData.diseaseSubtype || reportData.diseaseType || '',
reportOrg: reportData.reportOrg || '',
reportOrgPhone: reportData.reportOrgPhone || '',
reportDoc: reportData.reportDoc || '',
@@ -1466,7 +1470,7 @@ async function buildSubmitData() {
reportDate: formData.reportDate || null,
cardNameCode: 1, // 默认中华人民共和国传染病报告卡
registrationSource: 1, // 默认门诊
status: '',
status: null,
deptId: props.deptId || null,
doctorId: props.doctorId || null,
};

View File

@@ -23,7 +23,7 @@
<span class="descriptions-item-label">全选</span>
<el-switch v-model="chooseAll" @change="handelSwitchChange" />
<el-button class="ml20" type="primary" @click="handleCheck"> 核对通过 </el-button>
<el-button class="ml20 mr20" type="danger" @click="handleCancel"> 退回 </el-button>
<el-button class="ml20 mr20" type="danger" :disabled="hasDispensedSelected" @click="handleCancel"> 退回 </el-button>
</div>
</div>
<div
@@ -152,6 +152,7 @@
</div>
</template>
<script setup>
import {ref, computed} from 'vue';
import {adviceVerify, cancel, getPrescriptionList} from './api';
import {patientInfoList} from '../../components/store/patient.js';
import {formatDateStr} from '@/utils/index';
@@ -163,6 +164,11 @@ const type = ref(null);
const { proxy } = getCurrentInstance();
const loading = ref(false);
const chooseAll = ref(false);
const selectionTrigger = ref(0);
const hasDispensedSelected = computed(() => {
selectionTrigger.value;
return getSelectRows().some(item => item.dispenseStatus === 4);
});
const props = defineProps({
requestStatus: {
type: Number,
@@ -262,7 +268,9 @@ function getGroupMarkers() {
}
// 选择框改变时的处理
function handleSelectionChange(selection, row) {}
function handleSelectionChange(selection, row) {
selectionTrigger.value++;
}
/**
* 核对通过
@@ -291,7 +299,7 @@ function handleCancel() {
// 校验已发药的医嘱不允许退回
let dispensedItems = list.filter(item => item.dispenseStatus === 4);
if (dispensedItems.length > 0) {
proxy.$message.error('该医嘱已发药,无法退回');
proxy.$message.error('该药品已由药房发放,请先执行退药处理,不可直接退回');
return;
}
cancel(list).then((res) => {
@@ -310,12 +318,16 @@ function getSelectRows() {
// 获取选中的医嘱信息
let list = [];
prescriptionList.value.forEach((item, index) => {
list = [...list, ...proxy.$refs['tableRef' + index][0].getSelectionRows()];
const ref = proxy.$refs['tableRef' + index];
if (ref && ref[0]) {
list = [...list, ...ref[0].getSelectionRows()];
}
});
return list.map((item) => {
return {
requestId: item.requestId,
requestTable: item.adviceTable,
dispenseStatus: item.dispenseStatus,
};
});
}

View File

@@ -546,7 +546,7 @@
v-model="form.admissionTime"
type="datetime"
placeholder="选择入室时间"
value-format="YYYY-MM-DDTHH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
@@ -558,7 +558,7 @@
v-model="form.entryTime"
type="datetime"
placeholder="选择进室时间"
value-format="YYYY-MM-DDTHH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
@@ -570,7 +570,7 @@
v-model="form.anesStart"
type="datetime"
placeholder="选择麻醉开始时间"
value-format="YYYY-MM-DDTHH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
@@ -584,7 +584,7 @@
v-model="form.startTime"
type="datetime"
placeholder="选择切开时间"
value-format="YYYY-MM-DDTHH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
@@ -596,7 +596,7 @@
v-model="form.endTime"
type="datetime"
placeholder="选择手术结束时间"
value-format="YYYY-MM-DDTHH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
@@ -608,7 +608,7 @@
v-model="form.anesEnd"
type="datetime"
placeholder="选择麻醉结束时间"
value-format="YYYY-MM-DDTHH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
@@ -689,7 +689,7 @@
</el-dialog>
<!-- 手术申请查询弹窗 -->
<el-dialog :title="'手术申请查询'" v-model="showApplyDialog" width="1200px" @close="cancelApplyDialog">
<el-dialog :title="'手术申请查询'" v-model="showApplyDialog" width="1200px" @close="cancelApplyDialog" class="surgery-apply-dialog">
<!-- 查询条件区 -->
<el-form :model="applyQueryParams" ref="applyQueryRef" :inline="true" class="query-form">
<el-form-item label="手术单号" prop="surgeryNo">
@@ -741,16 +741,16 @@
</el-form>
<!-- 结果表格区 -->
<el-table
ref="applyTableRef"
v-loading="applyLoading"
:data="applyList"
row-key="surgeryNo"
@row-click="handleApplyRowClick"
:row-class-name="tableRowClassName"
style="width: 100%"
max-height="400"
:scroll="{ y: 400 }"
<el-table
ref="applyTableRef"
v-loading="applyLoading"
:data="applyList"
row-key="surgeryNo"
@row-click="handleApplyRowClick"
:row-class-name="tableRowClassName"
style="width: 100%"
max-height="340"
:scroll="{ y: 340 }"
>
<el-table-column type="selection" width="55" :selectable="handleSelectable" />
<el-table-column label="ID" align="center" width="80" fixed>
@@ -781,7 +781,7 @@
</el-table>
<!-- 底部分页区 -->
<div class="pagination-container" style="margin-top: 10px; padding-bottom: 10px">
<div class="pagination-container apply-pagination" style="margin-top: 10px; padding-bottom: 20px">
<pagination
v-show="applyTotal > 0"
:total="applyTotal"
@@ -792,10 +792,12 @@
@pagination="getSurgicalScheduleList"
/>
</div>
<!-- 分页与底部操作区之间的间隔 -->
<div style="height: 48px"></div>
<!-- 底部操作区 -->
<template #footer>
<div class="dialog-footer">
<div class="dialog-footer" style="margin-top: 24px; padding-top: 12px; border-top: 1px solid #ebeef5">
<el-button @click="cancelApplyDialog">取消</el-button>
<el-button type="primary" @click="confirmApply">确认</el-button>
</div>
@@ -830,8 +832,8 @@
</div>
<div style="padding: 10px">
<prescriptionlist :patientInfo="chargePatientInfo" ref="prescriptionRef"
:sourceBillNo="chargePatientInfo.sourceBillNo"
:generateSourceEnum="chargePatientInfo.generateSourceEnum" />
:generateSourceEnum="1"
:sourceBillNo="chargePatientInfo.sourceBillNo" />
<div class="overlay" v-if="disabled"></div>
</div>
</div>
@@ -2350,6 +2352,22 @@ function getRowClassName({ row, rowIndex }) {
margin-left: 10px;
}
/* 手术申请查询弹窗 — 分页与footer间距 */
.surgery-apply-dialog :deep(.el-dialog__body) {
padding-bottom: 32px;
}
.surgery-apply-dialog :deep(.el-dialog__footer) {
padding-top: 0;
}
.surgery-apply-dialog :deep(.apply-pagination) {
padding-bottom: 24px;
margin-bottom: 16px;
border-bottom: 1px solid #ebeef5;
}
.surgery-apply-dialog :deep(.apply-pagination .el-pagination) {
margin-right: 80px;
}
/* 选中行样式 */
:deep(.el-table .selected-row) {
background-color: #ecf5ff !important;
@@ -2360,3 +2378,21 @@ function getRowClassName({ row, rowIndex }) {
}
</style>
<style>
/* 手术申请查询弹窗 — 非 scoped 确保穿透 teleport */
.surgery-apply-dialog .apply-pagination {
padding-bottom: 24px !important;
margin-bottom: 16px !important;
border-bottom: 1px solid #ebeef5 !important;
}
.surgery-apply-dialog .apply-pagination .el-pagination {
margin-right: 80px !important;
}
.surgery-apply-dialog .el-dialog__body {
padding-bottom: 32px !important;
}
.surgery-apply-dialog .el-dialog__footer {
padding-top: 0 !important;
}
</style>