fix(#587): 请修复 Bug #587:[住院医生工作站-临床医嘱] 重大功能缺失:新增展示医嘱时缺少开始时间字段

根因:
- Bug #请修复 Bug #587 存在的问题

修复:
- ### 变更摘要
- #### 后端(Java)— 6 个文件修改
- 1. `openhis-server-new/.../dto/RequestBaseDto.java`**
- 新增 `startTime` 字段(`Date` 类型,`yyyy-MM-dd HH:mm:ss` 格式),使医嘱列表查询能返回开始时间
- 2. `openhis-server-new/.../dto/AdviceSaveDto.java`**
- 新增 `startTime` 字段(`Date` 类型),支持每条医嘱独立传入开始时间
- 3. `openhis-server-new/.../mapper/regdoctorstation/AdviceManageAppMapper.xml`**
- 三个 UNION ALL 子查询各新增一列:
- 药品(`advice_type=1`):`T1.effective_dose_start AS start_time`
- 耗材(`advice_type=2`):`T1.req_authored_time AS start_time`
- 诊疗/手术(`advice_type=3/6`):`T1.occurrence_start_time AS start_time`
- 4. `openhis-server-new/.../appservice/impl/AdviceManageAppServiceImpl.java`**
- `handMedication`、`handService`、`handDevice` 三个处理器中,每条医嘱的开始时间改为优先使用 DTO 级别的 `getStartTime()`,兜底使用参数级别的 `startTime`,实现每行独立开始时间
- #### 前端(Vue 3)— 2 个文件修改
- 5. `src/views/inpatientDoctor/home/components/order/index.vue`**
- 新增列**:在「类型」与「医嘱」列之间增加「开始时间」列,格式 `YYYY-MM-DD HH:mm:ss`
- 新增默认值**:`handleAddPrescription()` 新增时自动填充当前系统时间
- 新增校验函数** `validateStartTime()`:如果开始时间早于患者入院时间,弹窗拦截并提示
- 保存/签发校验**:`handleSaveSign`(单条保存)、`handleSaveBatch`(批量保存)、`handleSave`(签发)三个入口均加入开始时间校验
- 组套/历史医嘱**:`handleSaveGroup` 和 `handleSaveHistory` 均设置默认开始时间
- 提取 `defaultStartTimeFn()` 工具函数统一获取当前时间字符串
- 6. `src/views/inpatientDoctor/home/components/order/OrderForm.vue`**
- 三种医嘱类型(药品 `adviceType==1`、耗材 `adviceType==2`、诊疗 `v-else`)的编辑面板首行均新增「开始时间」`el-date-picker` 日期时间选择器
- 格式:`YYYY-MM-DD HH:mm:ss`,支持手动选择与键盘输入
- ### 全链路验证
- | 环节 | 状态 |
- |---|---|
- | **录入** → 编辑面板新增日期选择器 |  |
- | **保存** → 前端→API→Service→Entity→DB,逐行传递 startTime |  |
- | **查询** → DB→Mapper XML(3个UNION ALL)→DTO→前端展示 |  |
- | **修改** → 编辑回显 startTime → 修改再保存 |  |
- | **校验** → 早于入院时间拦截弹窗 |  |
- | **编译** → Java `mvn compile` 通过 |  |
- ### 注意事项
- 后端 `NurseBillingAppService`(护士划价)也有医嘱保存逻辑,但此 Bug 聚焦于住院医生工作站,护士站划价未做批量修改,如需同步可另行处理
This commit is contained in:
2026-05-29 00:02:55 +08:00
parent 4ccf272d4f
commit 2a55d36440
6 changed files with 114 additions and 6 deletions

View File

@@ -8,6 +8,10 @@ import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
@@ -26,6 +30,14 @@ public class AdviceSaveDto {
/** 医嘱类型 */
private Integer adviceType; // 1:药品 , 2: 耗材 , 3:项目
/**
* 医嘱开始时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 请求id
*/

View File

@@ -22,6 +22,12 @@ public class RequestBaseDto {
*/
private Integer adviceType; // 1:药品 , 2: 耗材 , 3:项目
/**
* 医嘱开始时间
*/
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 唯一标识
*/

View File

@@ -415,7 +415,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
}
// 保存时处理的字段属性
if (is_save) {
longMedicationRequest.setEffectiveDoseStart(startTime); // 医嘱开始时间
longMedicationRequest.setEffectiveDoseStart(regAdviceSaveDto.getStartTime() != null ? regAdviceSaveDto.getStartTime() : startTime); // 医嘱开始时间
longMedicationRequest
.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.MEDICATION_RES_NO.getPrefix(), 4));
longMedicationRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
@@ -503,7 +503,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
}
// 保存时处理的字段属性
if (is_save) {
tempMedicationRequest.setEffectiveDoseStart(startTime); // 医嘱开始时间
tempMedicationRequest.setEffectiveDoseStart(regAdviceSaveDto.getStartTime() != null ? regAdviceSaveDto.getStartTime() : startTime); // 医嘱开始时间
tempMedicationRequest
.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.MEDICATION_RES_NO.getPrefix(), 4));
tempMedicationRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
@@ -615,7 +615,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
}
// 保存时处理的字段属性
if (is_save) {
longServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间
longServiceRequest.setOccurrenceStartTime(regAdviceSaveDto.getStartTime() != null ? regAdviceSaveDto.getStartTime() : startTime); // 医嘱开始时间
longServiceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
longServiceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
longServiceRequest.setQuantity(new BigDecimal("1")); // 请求数量 | 诊疗的长期医嘱数量都是1
@@ -666,7 +666,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
}
// 保存时处理的字段属性
if (is_save) {
tempServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间
tempServiceRequest.setOccurrenceStartTime(regAdviceSaveDto.getStartTime() != null ? regAdviceSaveDto.getStartTime() : startTime); // 医嘱开始时间
tempServiceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
tempServiceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
tempServiceRequest.setQuantity(regAdviceSaveDto.getQuantity()); // 请求数量
@@ -812,7 +812,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
deviceRequest.setPatientId(regAdviceSaveDto.getPatientId()); // 患者
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(regAdviceSaveDto.getFounderOrgId()); // 开方人科室
deviceRequest.setReqAuthoredTime(startTime); // 医嘱开始时间
deviceRequest.setReqAuthoredTime(regAdviceSaveDto.getStartTime() != null ? regAdviceSaveDto.getStartTime() : startTime); // 医嘱开始时间
deviceRequest.setPerformLocation(regAdviceSaveDto.getLocationId()); // 发放科室
deviceRequest.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
deviceRequest.setPackageId(regAdviceSaveDto.getPackageId()); // 组套id
@@ -851,7 +851,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
deviceRequest.setPatientId(regAdviceSaveDto.getPatientId()); // 患者
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(regAdviceSaveDto.getFounderOrgId()); // 开方人科室
deviceRequest.setReqAuthoredTime(startTime); // 医嘱开始时间
deviceRequest.setReqAuthoredTime(regAdviceSaveDto.getStartTime() != null ? regAdviceSaveDto.getStartTime() : startTime); // 医嘱开始时间
deviceRequest.setPerformLocation(regAdviceSaveDto.getLocationId()); // 发放科室
deviceRequest.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
deviceRequest.setPackageId(regAdviceSaveDto.getPackageId()); // 组套id

View File

@@ -214,6 +214,7 @@
T1.dispense_per_duration AS dispense_per_duration,
T2.part_percent AS part_percent,
ccd.name AS condition_definition_name,
T1.effective_dose_start AS start_time,
T1.therapy_enum AS therapyEnum,
T1.sort_number AS sort_number,
T1.based_on_id AS based_on_id,
@@ -269,6 +270,7 @@
'' AS condition_definition_name,
2 AS therapyEnum,
99 AS sort_number,
T1.req_authored_time AS start_time,
T1.based_on_id AS based_on_id,
T1.device_def_id AS advice_definition_id
FROM wor_device_request AS T1
@@ -319,6 +321,7 @@
'' AS condition_definition_name,
COALESCE(T1.therapy_enum, 2) AS therapyEnum,
99 AS sort_number,
T1.occurrence_start_time AS start_time,
T1.based_on_id AS based_on_id,
T1.activity_id AS advice_definition_id
FROM wor_service_request AS T1

View File

@@ -16,6 +16,16 @@
']'
}}
</span>
<el-form-item label="开始时间:" prop="startTime">
<el-date-picker
v-model="row.startTime"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 190px; margin-right: 12px"
/>
</el-form-item>
<el-form-item prop="lotNumber" label="药房:">
<el-select
v-model="row.inventoryId"
@@ -304,6 +314,16 @@
row.unitCode_dictText
}}
</span>
<el-form-item label="开始时间:" prop="startTime" label-width="90">
<el-date-picker
v-model="row.startTime"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 190px; margin-right: 12px"
/>
</el-form-item>
<div class="form-group">
<el-select
v-model="row.lotNumber"
@@ -379,6 +399,16 @@
{{ Number(row.unitPrice).toFixed(2) }}
</template>
</span>
<el-form-item label="开始时间:" prop="startTime" label-width="90">
<el-date-picker
v-model="row.startTime"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 190px; margin-right: 12px"
/>
</el-form-item>
<div class="form-group">
<el-form-item
label="执行次数:"

View File

@@ -139,6 +139,13 @@
</span>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="170">
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.startTime || '-' }}
</span>
</template>
</el-table-column>
<el-table-column label="医嘱" align="center" prop="productName" width="300">
<template #default="scope">
<template v-if="getRowDisabled(scope.row)">
@@ -782,12 +789,15 @@ function handleAddPrescription() {
}
isAdding.value = true;
// 在数组最前方添加一行,让新增行显示在最上边
// 获取当前时间作为默认开始时间
const defaultStartTime = defaultStartTimeFn();
prescriptionList.value.unshift({
uniqueKey: nextId.value++,
showPopover: false,
isEdit: true,
statusEnum: 1,
therapyEnum: '1', // 默认为长期医嘱
startTime: defaultStartTime,
});
getGroupMarkers();
nextTick(() => {
@@ -812,6 +822,34 @@ function checkUnit(item, row) {
}
}
/**
* 校验医嘱开始时间不能早于患者入院时间
* @param {string} startTime 医嘱开始时间字符串
* @returns {boolean} true=通过, false=不通过
*/
function validateStartTime(startTime) {
if (!startTime || !patientInfo.value?.inHospitalTime) return true;
const startDate = new Date(startTime);
const inHospitalDate = new Date(patientInfo.value.inHospitalTime);
if (startDate < inHospitalDate) {
const pad = (n) => String(n).padStart(2, '0');
const d = inHospitalDate;
const timeStr = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
proxy.$modal.msgWarning('医嘱开始时间不能早于患者入院时间(' + timeStr + ')');
return false;
}
return true;
}
/**
* 获取当前时间的格式化字符串作为默认开始时间
*/
function defaultStartTimeFn() {
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
return now.getFullYear() + '-' + pad(now.getMonth() + 1) + '-' + pad(now.getDate()) + ' ' + pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds());
}
// 行双击打开编辑块:待保存、待签发医嘱均可编辑;已签发/已完成/停止不允许编辑
function clickRowDb(row, column, event) {
// 检查点击的是否是复选框
@@ -1136,6 +1174,12 @@ function handleSave() {
proxy.$modal.msgWarning('请先保存当前医嘱');
return;
}
// 校验所有待签发医嘱的开始时间
for (const item of filterPrescriptionList.value) {
if (item.statusEnum == 1 && item.requestId && !validateStartTime(item.startTime)) {
return;
}
}
// 如果可以编辑状态并且医嘱类型不存在删除掉
if (filterPrescriptionList.value[0].isEdit && !filterPrescriptionList.value[0].adviceType) {
prescriptionList.value.shift();
@@ -1329,6 +1373,10 @@ function handleCancelEdit(row, index) {
}
function handleSaveSign(row, index) {
// 校验医嘱开始时间
if (!validateStartTime(row.startTime)) {
return;
}
if (row.adviceType != 2) {
let itemNo = row.adviceType == 1 ? row.methodCode : row.adviceDefinitionId;
if (!itemNo) {
@@ -1413,6 +1461,13 @@ function handleSaveBatch() {
proxy.$modal.msgWarning('请先点击确定确认当前医嘱');
return;
}
// 校验开始时间(批量保存前验证所有待保存项)
const pendingItems = filterPrescriptionList.value.filter(item => item.statusEnum == 1 && !item.requestId);
for (const item of pendingItems) {
if (!validateStartTime(item.startTime)) {
return;
}
}
console.log('filterPrescriptionList=====>', JSON.stringify(filterPrescriptionList.value));
// 如果可以编辑状态并且医嘱类型不存在删除掉
@@ -1646,6 +1701,7 @@ function handleSaveGroup(orderGroupList) {
orgId: resolveOrgId(mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || '',
// 🔧 修复:同时存储 orgName确保树匹配不到时仍有中文名称可显示
orgName: findOrgName(mergedDetail.orgId || patientInfo.value?.inHospitalOrgId) || mergedDetail.orgName || patientInfo.value?.inHospitalOrgName || '',
startTime: mergedDetail.startTime || defaultStartTimeFn(),
dbOpType: prescriptionList.value[tempIndex].requestId ? '2' : '1',
conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value,
@@ -1690,6 +1746,7 @@ function handleSaveHistory(value) {
encounterId: patientInfo.value.encounterId,
accountId: accountId.value,
uniqueKey: undefined,
startTime: defaultStartTimeFn(),
dbOpType: value.requestId ? '2' : '1',
minUnitQuantity: value.quantity * value.partPercent,
conditionId: conditionId.value,