diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorStationAdviceController.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorStationAdviceController.java index 2e64d725f..44b57540a 100755 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorStationAdviceController.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/controller/DoctorStationAdviceController.java @@ -77,8 +77,10 @@ public class DoctorStationAdviceController { */ @PostMapping(value = "/save-advice") @RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试") - public R saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam) { - return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, AdviceOpType.SAVE_ADVICE.getCode()); + public R saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam, + @RequestParam(required = false, defaultValue = "1") String adviceOpType) { + // 🔧 Bug #445 修复:使用前端传入的 adviceOpType 参数(1=保存草稿,2=签发),而非硬编码 + return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, adviceOpType); } /** diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/AdviceProcessAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/AdviceProcessAppServiceImpl.java index 5688384cb..a86c601a5 100755 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/AdviceProcessAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/inhospitalnursestation/appservice/impl/AdviceProcessAppServiceImpl.java @@ -178,11 +178,24 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService { inpatientAdviceParam.setEncounterIds(null); Integer exeStatus = inpatientAdviceParam.getExeStatus(); inpatientAdviceParam.setExeStatus(null); - // requestStatus由前端tab传入,通过QueryWrapper自动添加到SQL外层WHERE过滤 + // 提取requestStatus手动处理,支持COMPLETED(3)和CHECK_VERIFIED(10)同时查询 + Integer requestStatus = inpatientAdviceParam.getRequestStatus(); + inpatientAdviceParam.setRequestStatus(null); // 构建查询条件 QueryWrapper queryWrapper = HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null); + // 手动拼接requestStatus条件:COMPLETED(3)时同时包含CHECK_VERIFIED(10) + // UNION查询外层列名为request_status(T1.status_enum AS request_status),不是status_enum + if (requestStatus != null) { + if (RequestStatus.COMPLETED.getValue().equals(requestStatus)) { + queryWrapper.in("request_status", + RequestStatus.COMPLETED.getValue(), RequestStatus.CHECK_VERIFIED.getValue()); + } else { + queryWrapper.eq("request_status", requestStatus); + } + } + // 手动拼接住院患者id条件 if (encounterIds != null && !encounterIds.isEmpty()) { List encounterIdList @@ -315,19 +328,29 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService { Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId(); Date checkDate = new Date(); if (!serviceRequestList.isEmpty()) { - // 更新服务请求状态已完成 - serviceRequestService.updateCompleteRequestStatus( - serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate); - List serviceRequests = serviceRequestService - .listByIds(serviceRequestList.stream().map(PerformInfoDto::getRequestId).collect(Collectors.toList())); - for (ServiceRequest serviceRequest : serviceRequests) { - // 判断医嘱类型 + List serviceReqIds = serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(); + // 先查询服务请求,按 categoryEnum 分流:检查类(23)走 CHECK_VERIFIED,其余走 COMPLETED + List allServiceRequests = serviceRequestService.listByIds(serviceReqIds); + List checkReqIds = allServiceRequests.stream() + .filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum())) + .map(ServiceRequest::getId).toList(); + List otherReqIds = allServiceRequests.stream() + .filter(sr -> !ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum())) + .map(ServiceRequest::getId).toList(); + // 检查类 → 已校对(CHECK_VERIFIED=10) + if (!checkReqIds.isEmpty()) { + serviceRequestService.updateCheckVerifiedStatus(checkReqIds, practitionerId, checkDate); + } + // 其他类 → 已完成(COMPLETED=3) + if (!otherReqIds.isEmpty()) { + serviceRequestService.updateCompleteRequestStatus(otherReqIds, practitionerId, checkDate); + } + // 处理转科/出院等特殊医嘱 + for (ServiceRequest serviceRequest : allServiceRequests) { if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) { - // 更新患者状态 待转科 encounterService.updateEncounterStatus(serviceRequest.getEncounterId(), EncounterZyStatus.PENDING_TRANSFER.getValue()); } else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) { - // 更新患者状态 待出院 encounterService.updateEncounterStatus(serviceRequest.getEncounterId(), EncounterZyStatus.AWAITING_DISCHARGE.getValue()); } @@ -442,6 +465,15 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService { List actUseExeList = this.assemblyActivity(activityList); // 处理诊疗执行 this.exeActivity(actUseExeList, exeDate); + // 检查类医嘱执行后,状态改为"待接收"(PENDING_RECEIVE=11) + List actReqIds = activityList.stream().map(AdviceExecuteDetailParam::getRequestId).toList(); + List executedReqs = serviceRequestService.listByIds(actReqIds); + List checkReqIds = executedReqs.stream() + .filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum())) + .map(ServiceRequest::getId).toList(); + if (!checkReqIds.isEmpty()) { + serviceRequestService.updatePendingReceiveStatus(checkReqIds); + } } return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱执行"})); diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/regdoctorstation/RequestFormManageAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/regdoctorstation/RequestFormManageAppMapper.xml index 69983e4e9..db9118d76 100755 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/regdoctorstation/RequestFormManageAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/regdoctorstation/RequestFormManageAppMapper.xml @@ -47,10 +47,19 @@ ) THEN 5 WHEN EXISTS ( SELECT 1 FROM wor_service_request ws - INNER JOIN lab_specimen ls ON ls.service_id = ws.id WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' - AND ls.collection_status_enum >= 1 + AND ws.status_enum = 12 ) THEN 4 + WHEN EXISTS ( + SELECT 1 FROM wor_service_request ws + WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' + AND ws.status_enum = 11 + ) THEN 3 + WHEN EXISTS ( + SELECT 1 FROM wor_service_request ws + WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' + AND ws.status_enum = 10 + ) THEN 2 WHEN EXISTS ( SELECT 1 FROM wor_service_request ws WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' diff --git a/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/RequestStatus.java b/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/RequestStatus.java index 501082d13..b310095ab 100755 --- a/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/RequestStatus.java +++ b/openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/RequestStatus.java @@ -57,7 +57,22 @@ public enum RequestStatus implements HisEnumInterface { /** * 未知 */ - UNKNOWN(9, "unknown", "未知"); + UNKNOWN(9, "unknown", "未知"), + + /** + * 已校对(检查申请:护士校对通过) + */ + CHECK_VERIFIED(10, "check_verified", "已校对"), + + /** + * 待接收(检查申请:等待医技科室接单) + */ + PENDING_RECEIVE(11, "pending_receive", "待接收"), + + /** + * 已接收(检查申请:医技科室已接单) + */ + CHECK_RECEIVED(12, "check_received", "已接收"); @EnumValue private final Integer value; diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/IServiceRequestService.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/IServiceRequestService.java index 30156f6e8..da91aef4e 100755 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/IServiceRequestService.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/IServiceRequestService.java @@ -39,6 +39,22 @@ public interface IServiceRequestService extends IService { */ void updateCompleteRequestStatus(List serReqIdList, Long practitionerId, Date checkDate); + /** + * 更新检查申请状态:已校对(护士校对检查申请后状态为CHECK_VERIFIED,而非COMPLETED) + * + * @param serReqIdList 服务请求id列表 + * @param practitionerId 校对人 + * @param checkDate 校对时间 + */ + void updateCheckVerifiedStatus(List serReqIdList, Long practitionerId, Date checkDate); + + /** + * 更新检查申请状态:待接收(护士执行检查申请后状态为PENDING_RECEIVE) + * + * @param serReqIdList 服务请求id列表 + */ + void updatePendingReceiveStatus(List serReqIdList); + /** * 获取执行过的诊疗数据 * diff --git a/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/impl/ServiceRequestServiceImpl.java b/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/impl/ServiceRequestServiceImpl.java index 9f34065f8..be6db03fa 100755 --- a/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/impl/ServiceRequestServiceImpl.java +++ b/openhis-server-new/openhis-domain/src/main/java/com/openhis/workflow/service/impl/ServiceRequestServiceImpl.java @@ -66,6 +66,31 @@ public class ServiceRequestServiceImpl extends ServiceImpl serReqIdList, Long practitionerId, Date checkDate) { + baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.CHECK_VERIFIED.getValue()) + .setPerformerCheckId(SecurityUtils.getLoginUser().getPractitionerId()).setCheckTime(DateUtils.getNowDate()), + new LambdaUpdateWrapper().in(ServiceRequest::getId, serReqIdList) + .eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode())); + } + + /** + * 更新检查申请状态:待接收(护士执行检查申请后状态为PENDING_RECEIVE) + * + * @param serReqIdList 服务请求id列表 + */ + @Override + public void updatePendingReceiveStatus(List serReqIdList) { + baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.PENDING_RECEIVE.getValue()), + new LambdaUpdateWrapper().in(ServiceRequest::getId, serReqIdList) + .eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode())); + } + /** * 获取执行过的诊疗数据 * diff --git a/openhis-ui-vue3/src/utils/medicalConstants.js b/openhis-ui-vue3/src/utils/medicalConstants.js index fb57629d9..7b3dd923c 100755 --- a/openhis-ui-vue3/src/utils/medicalConstants.js +++ b/openhis-ui-vue3/src/utils/medicalConstants.js @@ -34,6 +34,12 @@ export const RequestStatus = { ENDED: 7, /** 未知 */ UNKNOWN: 9, + /** 已校对(检查申请:护士校对通过) */ + CHECK_VERIFIED: 10, + /** 待接收(检查申请:等待医技科室接单) */ + PENDING_RECEIVE: 11, + /** 已接收(检查申请:医技科室已接单) */ + CHECK_RECEIVED: 12, }; /** @@ -48,6 +54,9 @@ export const RequestStatusDescriptions = { 6: '停嘱', 7: '不执行', 9: '未知', + 10: '已校对', + 11: '待接收', + 12: '已接收', }; /** diff --git a/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/api.js b/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/api.js index 0dbf88de0..52c814ff4 100755 --- a/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/api.js +++ b/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/api.js @@ -25,9 +25,9 @@ export function getAdviceBaseInfo(queryParams) { /** * 保存处方(单条) * @param {Object} data - 处方数据 - * @param {String} adviceOpType - 医嘱操作类型:'0'=保存草稿,'1'=签发医嘱 + * @param {String} adviceOpType - 医嘱操作类型:'1'=保存草稿(SAVE_ADVICE),'2'=签发(SIGN_ADVICE) */ -export function savePrescription(data, adviceOpType = '0') { +export function savePrescription(data, adviceOpType = '1') { return request({ url: '/doctor-station/advice/save-advice', method: 'post', diff --git a/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue b/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue index 96847cb5b..92a29678a 100755 --- a/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue +++ b/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue @@ -1174,7 +1174,9 @@ function handleSaveSign(row, index) { cleanRow.sourceBillNo = props.patientInfo.sourceBillNo; } // 🔧 门诊计费场景:保存为草稿,让药品出现在临时医嘱弹窗"已引用计费药品(待生成医嘱)"中 - const adviceOpType = props.patientInfo.sourceBillNo ? '0' : '1' + // 🔧 修复:后端 AdviceOpType 枚举:'1'=SAVE_ADVICE(草稿), '2'=SIGN_ADVICE(签发) + // 之前传 '0' 后端不识别,导致直接创建了已签发(statusEnum=2)的记录 + const adviceOpType = props.patientInfo.sourceBillNo ? '1' : '1' savePrescription({ adviceSaveList: [cleanRow] }, adviceOpType).then((res) => { if (res.code === 200) { proxy.$modal.msgSuccess('保存成功'); diff --git a/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/medicalExaminations.vue b/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/medicalExaminations.vue index c2c8cf072..edc5bdb5a 100755 --- a/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/medicalExaminations.vue +++ b/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/medicalExaminations.vue @@ -539,7 +539,7 @@ const submit = () => { requestFormId: requestFormId, name: selectedNames, descJson: JSON.stringify(submitForm), - categoryEnum: '22', + categoryEnum: '23', }).then((res) => { if (res.code === 200) { ElMessage.success(res.msg || (props.isEditMode ? '修改成功' : '保存成功')); diff --git a/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/index.vue b/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/index.vue index ab1a6a1d1..8e13d9518 100755 --- a/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/index.vue +++ b/openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/index.vue @@ -230,6 +230,8 @@ 待保存 待签发 + 已校对 + 待接收 已完成 停止 {{ scope.row.chargeStatus_enumText }} diff --git a/openhis-ui-vue3/src/views/surgicalschedule/index.vue b/openhis-ui-vue3/src/views/surgicalschedule/index.vue index de0706b21..a6a744c2c 100755 --- a/openhis-ui-vue3/src/views/surgicalschedule/index.vue +++ b/openhis-ui-vue3/src/views/surgicalschedule/index.vue @@ -1519,13 +1519,14 @@ function handleMedicalAdvice(row) { } // 🔧 每次打开临时医嘱都重新拉取最新数据,确保计费弹窗签发后数据自动更新 - // 先清空旧数据 + // 🔧 修复 Bug #446: 先保存旧数据再清空,避免竟态条件 + const prevAdvices = [...temporaryAdvices.value] temporaryBillingMedicines.value = [] temporaryAdvices.value = [] // 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId),保留签名状态 - const hasSubmittedAdvices = temporaryAdvices.value.length > 0 && - temporaryAdvices.value[0]?.originalMedicine?.encounterId === row.visitId && - temporaryAdvices.value.some(a => a.originalMedicine?.requestId); + const hasSubmittedAdvices = prevAdvices.length > 0 && + prevAdvices[0]?.originalMedicine?.encounterId === row.visitId && + prevAdvices.some(a => a.originalMedicine?.requestId); temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置 temporaryMedicalLoading.value = true // 🔧 新增:开始加载 @@ -1585,7 +1586,13 @@ function handleMedicalAdvice(row) { adviceType: item.adviceType || contentData.adviceType || item.advice_type || null, chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null, definitionId: item.definitionId || contentData.definitionId || item.definition_id || null, - definitionDetailId: item.definitionDetailId || contentData.definitionDetailId || item.definition_detail_id || null + definitionDetailId: item.definitionDetailId || contentData.definitionDetailId || item.definition_detail_id || null, + // 🔧 修复:传递 requestId,临时医嘱签署时需要用于更新已有记录而非新建 + requestId: item.requestId || null, + // 🔧 保留原始 contentJson,临时医嘱签发时需要展开所有字段传给后端 + contentJson: item.contentJson || item.content_json || null, + // 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态 + _signed: item.statusEnum === 2 }; } catch (e) { // 如果解析失败,使用顶层数据 - 兼容 snake_case 和 camelCase 以及后端不同字段名 @@ -1606,7 +1613,13 @@ function handleMedicalAdvice(row) { adviceType: item.adviceType || item.advice_type || null, chargeItemId: item.chargeItemId || item.charge_item_id || null, definitionId: item.definitionId || item.definition_id || null, - definitionDetailId: item.definitionDetailId || item.definition_detail_id || null + definitionDetailId: item.definitionDetailId || item.definition_detail_id || null, + // 🔧 修复:传递 requestId,临时医嘱签署时需要用于更新已有记录而非新建 + requestId: item.requestId || null, + // 🔧 保留原始 contentJson,临时医嘱签发时需要展开所有字段传给后端 + contentJson: item.contentJson || item.content_json || null, + // 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态 + _signed: item.statusEnum === 2 }; } }); @@ -1694,73 +1707,134 @@ function closeTemporaryMedical() { // 处理临时医嘱提交 // 🔧 修复:提交成功后,更新 temporaryAdvices 中的 requestId,以便下次提交时执行更新操作 function handleTemporaryMedicalSubmit(data) { - // 🔧 修复:使用用户修改后的数据,而不是重新加载数据 - // 这样可以确保用户修改的内容(如剂量)在保存后仍然正确显示 - if (data.temporaryAdvices && data.temporaryAdvices.length > 0) { - // 🔧 关键修复:更新 temporaryAdvices 中的数据,保留用户的修改 - // 但是需要从后端返回的数据中获取 requestId,以便下次提交时执行更新操作 - // 如果后端返回了医嘱ID(requestId),我们需要更新到 temporaryAdvices 中 - temporaryAdvices.value = data.temporaryAdvices.map((advice, index) => { - const originalMedicine = advice.originalMedicine || {} - - // 🔧 关键修复:从后端返回的数据中获取 requestId(如果有) - // 后端返回的医嘱ID可能需要通过某个字段获取,这里暂时假设后端会返回 - // 如果后端返回的 response 中包含医嘱ID,需要更新到 originalMedicine 中 - // 这里暂时保留原逻辑,等待用户提供后端返回的具体数据结构 - - return { - ...advice, - // 确保 originalMedicine 包含所有必要字段 - originalMedicine: { - ...originalMedicine, - // 保留用户的修改 - contentJson: originalMedicine.contentJson || JSON.stringify({ - dose: advice.dosage, - methodCode: advice.usage, - rateCode: advice.frequency, - quantity: advice.quantity, - totalPrice: advice.totalPrice + // 🔧 Bug #445 修复:提交成功后重新拉取数据,确保"待生成"列表正确更新 + if (data.patientInfo && data.patientInfo.visitId) { + const row = { visitId: data.patientInfo.visitId, operCode: data.patientInfo.operCode } + temporarySigned.value = true + ElMessage.success('临时医嘱已生成(已签发)') + + // 重新拉取最新数据,后端已将 statusEnum 从 1(草稿) 更新为 2(已签发) + getPrescriptionList(row.visitId, 6, row.operCode).then((res) => { + if (res.code === 200 && res.data) { + const seenIds = new Set() + const filteredItems = res.data.filter(item => { + if (item.encounterId !== row.visitId) return false + const at = Number(item.adviceType ?? item.advice_type) + if (at !== 1 && at !== 2) return false + const medicineName = item.adviceName || item.advice_name + if (!medicineName || medicineName.trim() === '') return false + const excludedKeywords = ['术', '超声', '多普勒', '检查', '检验', '彩超', 'X线', 'CT', 'MRI', '扫描', '造影'] + if (excludedKeywords.some(kw => medicineName.includes(kw))) return false + const itemId = item.requestId || item.id + if (itemId && seenIds.has(itemId)) return false + if (itemId) seenIds.add(itemId) + return true + }) + + const draftItems = filteredItems.filter(item => item.statusEnum === 1) + const activeItems = filteredItems.filter(item => item.statusEnum === 2) + + // 更新待生成列表:只保留未生成的项目 + temporaryBillingMedicines.value = draftItems.map(item => { + try { + const jsonContent = item.contentJson || item.content_json + const contentData = jsonContent ? JSON.parse(jsonContent) : {} + return { + medicineName: contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || item.chargeName || item.charge_name || contentData.itemName || contentData.item_name || '未知药品', + specification: contentData.volume || contentData.specification || item.volume || item.specification || '', + quantity: contentData.quantity || item.quantity || 0, + batchNumber: contentData.lotNumber || contentData.lot_number || item.lotNumber || item.lot_number || '', + unitPrice: contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0, + subtotal: contentData.totalPrice || contentData.total_price || item.totalPrice || item.total_price || + (contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0) * + (contentData.quantity || item.quantity || 0), + insuranceType: (contentData.insuranceType || contentData.insurance_type) === 1 ? '医保' : (item.insuranceType === 1 || item.insurance_type === 1) ? '医保' : '自费', + adviceDefinitionId: item.adviceDefinitionId || contentData.adviceDefinitionId || item.advice_definition_id || null, + adviceTableName: item.adviceTableName || contentData.adviceTableName || item.advice_table_name || null, + adviceType: item.adviceType || contentData.adviceType || item.advice_type || null, + chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null, + definitionId: item.definitionId || contentData.definitionId || item.definition_id || null, + definitionDetailId: item.definitionDetailId || contentData.definitionDetailId || item.definition_detail_id || null, + // 🔧 修复:传递 requestId + requestId: item.requestId || null, + // 🔧 保留原始 contentJson,临时医嘱签发时需要展开所有字段传给后端 + contentJson: item.contentJson || item.content_json || null, + // 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态 + _signed: item.statusEnum === 2 + } + } catch (e) { + return { + medicineName: item.adviceName || item.advice_name || item.chargeName || '', + specification: item.specification || item.volume || '', + quantity: item.quantity || item.quantity_value || item.quantityValue || 0, + batchNumber: item.lotNumber || item.lot_number || '', + unitPrice: item.unitPrice || item.unit_price || 0, + subtotal: item.totalPrice || item.total_price || 0, + insuranceType: (item.insuranceType || item.insurance_type) === 1 ? '医保' : '自费', + adviceDefinitionId: item.adviceDefinitionId || item.advice_definition_id || null, + adviceTableName: item.adviceTableName || item.advice_table_name || null, + adviceType: item.adviceType || item.advice_type || null, + chargeItemId: item.chargeItemId || item.charge_item_id || null, + definitionId: item.definitionId || item.definition_id || null, + definitionDetailId: item.definitionDetailId || item.definition_detail_id || null, + // 🔧 修复:传递 requestId + requestId: item.requestId || null, + // 🔧 保留原始 contentJson,临时医嘱签发时需要展开所有字段传给后端 + contentJson: item.contentJson || item.content_json || null, + // 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态 + _signed: item.statusEnum === 2 + } + } + }) + + // 更新已生成列表 + temporaryAdvices.value = activeItems.map((item, index) => { + try { + const jsonContent = item.contentJson || item.content_json + const contentData = jsonContent ? JSON.parse(jsonContent) : {} + const medicineName = contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || '' + const spec = contentData.volume || contentData.specification || item.volume || item.specification || '' + const specMatch = spec.match(/(\d+)(\D+)/) + const specValue = specMatch ? parseInt(specMatch[1]) : 1 + const specUnit = specMatch ? specMatch[2] : 'ml' + const dosage = specValue * (contentData.quantity || item.quantity || 1) + let usageCode = contentData.methodCode || 'iv' + return { + id: index + 1, adviceName: medicineName, dosage, unit: specUnit, + usage: usageCode, frequency: '临时', + executeTime: new Date().toLocaleString('zh-CN'), + originalMedicine: { ...item, medicineName, specification: spec, quantity: contentData.quantity || item.quantity || 1, encounterId: row.visitId } + } + } catch (e) { + return { + id: index + 1, adviceName: item.adviceName || '', dosage: 1, unit: 'ml', + usage: 'iv', frequency: '临时', executeTime: new Date().toLocaleString('zh-CN'), + originalMedicine: { ...item, medicineName: item.adviceName || '', specification: item.volume || '', quantity: item.quantity || 1, encounterId: row.visitId } + } + } + }) + } + }).catch(() => { + // 拉取失败时使用本地过滤作为兜底 + if (data.temporaryAdvices && data.temporaryAdvices.length > 0) { + const submittedKeys = new Set( + data.temporaryAdvices.map(a => { + const om = a.originalMedicine || {} + return `${om.medicineName || a.adviceName || ''}|||${om.specification || ''}|||${om.quantity || 0}` + }).filter(k => k !== '|||0') + ) + if (submittedKeys.size > 0) { + temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => { + return !submittedKeys.has(`${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`) }) } + temporaryAdvices.value = data.temporaryAdvices } }) - - // 🔧 修复 Bug #445: 使用稳定的字段组合匹配已提交项目,而不是依赖可能为空的 requestId/chargeItemId - // 构建已提交项目的匹配键集合(药品名称 + 规格 + 数量) - const submittedKeys = new Set( - (data.temporaryAdvices || []) - .map(a => { - const om = a.originalMedicine || {} - const name = om.medicineName || om.adviceName || om.advice_name || a.adviceName || '' - const spec = om.specification || om.volume || '' - const qty = om.quantity || 0 - return `${name}|||${spec}|||${qty}` - }) - .filter(k => k !== '|||0') // 过滤掉空项 - ) - - if (submittedKeys.size > 0) { - temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => { - const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}` - return !submittedKeys.has(key) - }) - } else { - // 如果没有任何匹配键,清空待生成列表(所有项目都已提交) - temporaryBillingMedicines.value = [] - } - } else { - // 如果没有传递数据,则清空 temporaryAdvices.value = [] temporaryBillingMedicines.value = [] } - - // 🔧 设置签名状态,保持按钮名称一致 - temporarySigned.value = true - - // 显示成功提示,不关闭弹窗,让用户可以查看已签发的医嘱状态 - ElMessage.success('临时医嘱已生成(已签发),可继续查看或修改') - } function handleTemporaryMedicalCancel() { // 🔧 修复:用户点击取消时才清空数据,因为用户可能要放弃修改 @@ -1911,7 +1985,13 @@ function handleQuoteBilling() { orgId: contentData.orgId || item.orgId || contentData.positionId || item.positionId || userStore.orgId, positionId: contentData.positionId || item.positionId || userStore.orgId, definitionId: contentData.definitionId || item.definitionId, - definitionDetailId: contentData.definitionDetailId || item.definitionDetailId + definitionDetailId: contentData.definitionDetailId || item.definitionDetailId, + // 🔧 修复:传递 requestId + requestId: item.requestId || null, + // 🔧 保留原始 contentJson,临时医嘱签发时需要展开所有字段传给后端 + contentJson: item.contentJson || item.content_json || null, + // 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态 + _signed: item.statusEnum === 2 } } catch (e) { return { @@ -1927,7 +2007,13 @@ function handleQuoteBilling() { orgId: item.orgId || item.positionId || userStore.orgId, positionId: item.positionId || userStore.orgId, definitionId: item.definitionId, - definitionDetailId: item.definitionDetailId + definitionDetailId: item.definitionDetailId, + // 🔧 修复:传递 requestId + requestId: item.requestId || null, + // 🔧 保留原始 contentJson,临时医嘱签发时需要展开所有字段传给后端 + contentJson: item.contentJson || item.content_json || null, + // 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态 + _signed: item.statusEnum === 2 } } }) diff --git a/openhis-ui-vue3/src/views/surgicalschedule/temporaryMedical.vue b/openhis-ui-vue3/src/views/surgicalschedule/temporaryMedical.vue index 99c9cc0db..f0314e159 100755 --- a/openhis-ui-vue3/src/views/surgicalschedule/temporaryMedical.vue +++ b/openhis-ui-vue3/src/views/surgicalschedule/temporaryMedical.vue @@ -317,10 +317,11 @@ const getMethodCodeDict = computed(() => { return dict }) -// 🔧 修复 Bug #446: 检查计费药品是否已全部提交(有 requestId),用于区分"首次签名"和"已提交重开" +// 🔧 检查是否已全部签发(statusEnum=2),用于控制"一键签名"按钮是否禁用 +// 注意:不能依赖 requestId,因为草稿记录也有 requestId const allItemsSubmitted = computed(() => { const meds = props.billingMedicines || [] - return meds.length > 0 && meds.every(m => m.requestId) + return meds.length > 0 && meds.every(m => m._signed) }) // 展开/收起控制 @@ -425,32 +426,20 @@ const displayAdvices = ref([]) // 初始化 displayAdvices const initDisplayAdvices = () => { - // 只要有用户修改过的数据,就优先使用用户修改后的数据 - // 避免自动转换覆盖用户的修改 + // 区域二只显示已生成(已签发)的数据,没有时保持为空,不自动转换区域一的草稿 if (props.temporaryAdvices && props.temporaryAdvices.length > 0) { - // 🔧 修复:将旧编码映射到新编码 displayAdvices.value = props.temporaryAdvices.map(mapUsageCode) } else { - // 否则自动转换第一区域的已引用计费药品 - displayAdvices.value = convertedAdvices.value + displayAdvices.value = [] } } // 初始化 initDisplayAdvices() -// 组件挂载时,如果没有传入临时医嘱数据,将自动转换后的数据同步到父组件 -// 确保修改可以被父组件保存,下次打开仍然能看到 onMounted(() => { - - - // 初始化 displayAdvices initDisplayAdvices() - - if ((!props.temporaryAdvices || props.temporaryAdvices.length === 0) && convertedAdvices.value.length > 0) { - console.log('=== onMounted 触发 emit ===') - emit('update:temporary-advices', convertedAdvices.value) - } + // 🔧 修复:不再自动把区域一草稿转为区域二数据 }) // 🔧 新增:监听 temporary-advices prop 的变化,同步更新 displayAdvices @@ -465,23 +454,9 @@ watch(() => props.temporaryAdvices, (newVal, oldVal) => { } }, { deep: true, immediate: true }) -// 监听第一区域的计费药品变化,如果第一区域更新了,并且用户还没修改过第二区域数据,自动更新第二区域 +// 🔧 修复:不再自动把区域一草稿转为区域二数据,区域二只显示已签发的记录 watch(() => props.billingMedicines, (newVal) => { - console.log('=== watch billingMedicines 被调用 ===') - console.log('=== newVal ===', newVal) - console.log('=== props.temporaryAdvices ===', props.temporaryAdvices) - console.log('=== props.temporaryAdvices.length ===', props.temporaryAdvices?.length) - - // 🔧 修复:只有当 temporary-advices 为空时才自动更新,避免覆盖用户的修改 - if (!props.temporaryAdvices || props.temporaryAdvices.length === 0) { - if (newVal.length > 0) { - console.log('=== watch 触发 emit ===') - displayAdvices.value = convertedAdvices.value - emit('update:temporary-advices', convertedAdvices.value) - } - } else { - console.log('=== watch 不触发 emit,因为 props.temporaryAdvices 已有数据 ===') - } + // 空 watcher,仅保留结构避免破坏其他逻辑 }, { deep: true }) // 🔧 新增:监听字典加载,确保字典加载后编辑弹窗能正确显示 @@ -697,7 +672,9 @@ const handleSubmit = async () => { } // 检查是否有医嘱数据 - if (displayAdvices.value.length === 0) { + // 🔧 修复:签发应该处理区域一中所有待签发的药品(billingMedicines),而非区域二中已有数据(displayAdvices) + const itemsToSign = convertedAdvices.value + if (itemsToSign.length === 0) { ElMessage.warning('没有可保存的医嘱数据') return } @@ -709,7 +686,7 @@ const handleSubmit = async () => { // 构建保存医嘱的请求参数 const saveData = { organizationId: props.patientInfo.orgId || props.patientInfo.organizationId || 1, // 使用计费时的orgId - adviceSaveList: displayAdvices.value.map((advice, index) => { + adviceSaveList: itemsToSign.map((advice, index) => { // 获取原始药品数据 const originalMedicine = advice.originalMedicine @@ -764,53 +741,44 @@ const handleSubmit = async () => { // 重新序列化contentJson const updatedContentJson = JSON.stringify(contentJsonData); - // 构造请求参数(与门诊医生工作站完全一致) + // 🔧 构造请求参数:与计费弹窗 handleSave 保持完全一致的模式 + // 先展开原始 contentJson 的所有字段,然后用当前值覆盖(保证不丢字段) return { + ...contentJsonData, // 基础信息 - // 🔧 修复:dbOpType 的判断逻辑 - 如果有 requestId 则为修改,否则为新增 - // 但对于从计费药品转换来的数据,即使没有 requestId,也应该先更新 chargeItemId dbOpType: originalMedicine?.requestId ? '2' : (originalMedicine?.chargeItemId ? '2' : '1'), - adviceType: originalMedicine?.adviceType || 1, // 使用原始类型或默认1 + adviceType: originalMedicine?.adviceType || 1, requestId: originalMedicine?.requestId, chargeItemId: originalMedicine?.chargeItemId, contentJson: updatedContentJson, categoryCode: contentJsonData.categoryCode || originalMedicine?.categoryCode, - pharmacologyCategoryCode: contentJsonData.pharmacologyCategoryCode || originalMedicine?.pharmacologyCategoryCode, partPercent: originalMedicine?.partPercent || contentJsonData.partPercent || 1, - partAttributeEnum: originalMedicine?.partAttributeEnum || contentJsonData.partAttributeEnum, executeNum: contentJsonData.executeNum || originalMedicine?.executeNum || 1, - prescriptionNo: originalMedicine?.prescriptionNo, // 数量和单位:使用重新计算后的数量 quantity: quantity, - dispensePerDuration: originalMedicine?.dispensePerDuration || contentJsonData.dispensePerDuration, unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit, unitPrice: unitPrice, - // 总价根据新的数量重新计算 totalPrice: totalPrice, definitionId: originalMedicine?.definitionId ? String(originalMedicine.definitionId) : contentJsonData.definitionId ? String(contentJsonData.definitionId) : advice.definitionId ? String(advice.definitionId) : null, definitionDetailId: originalMedicine?.definitionDetailId ? String(originalMedicine.definitionDetailId) : contentJsonData.definitionDetailId ? String(contentJsonData.definitionDetailId) : advice.definitionDetailId ? String(advice.definitionDetailId) : null, lotNumber: originalMedicine?.lotNumber || contentJsonData.lotNumber, // 状态和类型 - statusEnum: originalMedicine?.statusEnum || 2, // 默认状态:已发送 categoryEnum: originalMedicine?.categoryEnum || contentJsonData.categoryEnum || 1, - // 药品/诊疗信息 - 🔧 修复:优先使用originalMedicine中的adviceDefinitionId和adviceTableName - // 🔧 关键修复:确保adviceDefinitionId不为null,使用definitionId作为后备 - adviceDefinitionId: originalMedicine?.adviceDefinitionId || contentJsonData.adviceDefinitionId || advice.definitionId || contentJsonData.definitionId || definitionId, + // 药品/诊疗信息 + adviceDefinitionId: originalMedicine?.adviceDefinitionId || contentJsonData.adviceDefinitionId || advice.definitionId || contentJsonData.definitionId, adviceTableName: originalMedicine?.adviceTableName || contentJsonData.adviceTableName || 'med_medication_definition', adviceName: advice.adviceName, - minUnitQuantity: originalMedicine?.minUnitQuantity || contentJsonData.minUnitQuantity, // 患者和就诊信息 patientId: props.patientInfo.patientId, - practitionerId: currentUser.value.id || originalMedicine?.practitionerId, // 开方医生 + practitionerId: currentUser.value.id || originalMedicine?.practitionerId, locationId: originalMedicine?.positionId || contentJsonData.locationId || props.patientInfo.orgId || props.patientInfo.positionId, positionId: originalMedicine?.positionId || contentJsonData.positionId, orgId: originalMedicine?.orgId || props.patientInfo.orgId || props.patientInfo.positionId, - performLocation: originalMedicine?.performLocation || contentJsonData.performLocation, - founderOrgId: currentUser.value.id || originalMedicine?.founderOrgId, // 开方人科室 + founderOrgId: currentUser.value.id || originalMedicine?.founderOrgId, encounterId: props.patientInfo.visitId || contentJsonData.encounterId || originalMedicine?.encounterId, accountId: contentJsonData.accountId || originalMedicine?.accountId, conditionId: contentJsonData.conditionId || originalMedicine?.conditionId, @@ -818,12 +786,10 @@ const handleSubmit = async () => { conditionDefinitionId: originalMedicine?.conditionDefinitionId || contentJsonData.conditionDefinitionId, // 治疗信息 - therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1, // 默认临时医嘱 - // 🔧 修复:methodCode 使用编码,而不是中文名称 - methodCode: methodCode, // 用法(使用编码) - rateCode: advice.frequency, // 频次 + therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1, + methodCode: methodCode, + rateCode: advice.frequency, dose: advice.dosage, - firstDose: originalMedicine?.firstDose || contentJsonData.firstDose, doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit, skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag, injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag, @@ -832,55 +798,30 @@ const handleSubmit = async () => { groupId: originalMedicine?.groupId, packageId: originalMedicine?.packageId || contentJsonData.packageId, - // 诊疗活动信息 - activityId: contentJsonData.adviceDefinitionId || originalMedicine?.adviceDefinitionId, // 对于诊疗类型,使用 adviceDefinitionId 作为 activity_id - // 医保信息 ybClassEnum: originalMedicine?.ybClassEnum || contentJsonData.ybClassEnum, - // 中药信息 - chineseHerbsDoseQuantity: originalMedicine?.chineseHerbsDoseQuantity || contentJsonData.chineseHerbsDoseQuantity || 1 + // 🔧 补充:手术计费上下文 + generateSourceEnum: 6, + sourceBillNo: props.patientInfo?.operCode || originalMedicine?.sourceBillNo || '' } }) } - // 调用保存医嘱接口 - // 如果已签名,则执行签发操作('1'),否则执行保存操作('0') - const adviceOpType = isSigned.value ? '1' : '0' - const response = await savePrescription(saveData, adviceOpType) + // 🔧 修复:使用 savePrescription + adviceOpType='2'(后端 AdviceOpType: '2'=SIGN_ADVICE 签发) + const response = await savePrescription(saveData, '2') if (response.code === 200) { ElMessage.success('临时医嘱保存成功') - // 🔧 关键修复:处理后端返回的医嘱 ID - // 后端返回的 data 数组只包含新创建的医嘱记录的 ID,不包含已更新的记录 - // 我们需要正确地映射这些 ID 到对应的临时医嘱数据 - if (response.data && Array.isArray(response.data) && response.data.length > 0) { - // 创建一个索引,记录哪些记录是新创建的 - let newDataIndex = 0 - - displayAdvices.value.forEach((advice, index) => { - const originalMedicine = advice.originalMedicine || {} - // 如果这个记录没有 requestId,说明是新创建的 - if (!originalMedicine.requestId) { - if (newDataIndex < response.data.length) { - // 更新 originalMedicine 中的 requestId - if (!displayAdvices.value[index].originalMedicine) { - displayAdvices.value[index].originalMedicine = {} - } - displayAdvices.value[index].originalMedicine.requestId = response.data[newDataIndex] - newDataIndex++ - } - } - }) - } + // 🔧 签名成功,后端已更新已有记录的 statusEnum 为 2 + // 不需要手动更新 requestId(签名操作是 UPDATE,不是 INSERT,后端不返回新 ID) - // 🔧 修复:将保存后的医嘱数据(包含用户的修改和后端返回的 requestId)传递给父组件 - // 这样父组件可以使用用户修改后的数据,而不是重新加载数据 + // 🔧 修复:将保存后的医嘱数据传递给父组件 const submitData = { patientInfo: props.patientInfo, billingMedicines: props.billingMedicines, - temporaryAdvices: displayAdvices.value, // 使用用户修改后的数据,包含后端返回的 requestId + temporaryAdvices: itemsToSign, signature: { doctorName: currentUser.value.name, signatureTime: signatureTime.value