bug 445 497 565

This commit is contained in:
Ranyunqiao
2026-05-25 15:49:49 +08:00
parent 232577caaa
commit 5132de3680
13 changed files with 316 additions and 177 deletions

View File

@@ -77,8 +77,10 @@ public class DoctorStationAdviceController {
*/ */
@PostMapping(value = "/save-advice") @PostMapping(value = "/save-advice")
@RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试") @RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试")
public R<?> saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam) { public R<?> saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam,
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, AdviceOpType.SAVE_ADVICE.getCode()); @RequestParam(required = false, defaultValue = "1") String adviceOpType) {
// 🔧 Bug #445 修复:使用前端传入的 adviceOpType 参数1=保存草稿2=签发),而非硬编码
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, adviceOpType);
} }
/** /**

View File

@@ -178,11 +178,24 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
inpatientAdviceParam.setEncounterIds(null); inpatientAdviceParam.setEncounterIds(null);
Integer exeStatus = inpatientAdviceParam.getExeStatus(); Integer exeStatus = inpatientAdviceParam.getExeStatus();
inpatientAdviceParam.setExeStatus(null); inpatientAdviceParam.setExeStatus(null);
// requestStatus由前端tab传入通过QueryWrapper自动添加到SQL外层WHERE过滤 // 提取requestStatus手动处理支持COMPLETED(3)和CHECK_VERIFIED(10)同时查询
Integer requestStatus = inpatientAdviceParam.getRequestStatus();
inpatientAdviceParam.setRequestStatus(null);
// 构建查询条件 // 构建查询条件
QueryWrapper<InpatientAdviceParam> queryWrapper QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null); = HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
// 手动拼接requestStatus条件COMPLETED(3)时同时包含CHECK_VERIFIED(10)
// UNION查询外层列名为request_statusT1.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条件 // 手动拼接住院患者id条件
if (encounterIds != null && !encounterIds.isEmpty()) { if (encounterIds != null && !encounterIds.isEmpty()) {
List<Long> encounterIdList List<Long> encounterIdList
@@ -315,19 +328,29 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId(); Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
Date checkDate = new Date(); Date checkDate = new Date();
if (!serviceRequestList.isEmpty()) { if (!serviceRequestList.isEmpty()) {
// 更新服务请求状态已完成 List<Long> serviceReqIds = serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList();
serviceRequestService.updateCompleteRequestStatus( // 先查询服务请求,按 categoryEnum 分流:检查类(23)走 CHECK_VERIFIED其余走 COMPLETED
serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate); List<ServiceRequest> allServiceRequests = serviceRequestService.listByIds(serviceReqIds);
List<ServiceRequest> serviceRequests = serviceRequestService List<Long> checkReqIds = allServiceRequests.stream()
.listByIds(serviceRequestList.stream().map(PerformInfoDto::getRequestId).collect(Collectors.toList())); .filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
for (ServiceRequest serviceRequest : serviceRequests) { .map(ServiceRequest::getId).toList();
// 判断医嘱类型 List<Long> 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())) { if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) {
// 更新患者状态 待转科
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(), encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
EncounterZyStatus.PENDING_TRANSFER.getValue()); EncounterZyStatus.PENDING_TRANSFER.getValue());
} else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) { } else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) {
// 更新患者状态 待出院
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(), encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
EncounterZyStatus.AWAITING_DISCHARGE.getValue()); EncounterZyStatus.AWAITING_DISCHARGE.getValue());
} }
@@ -442,6 +465,15 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
List<ServiceRequestUseExe> actUseExeList = this.assemblyActivity(activityList); List<ServiceRequestUseExe> actUseExeList = this.assemblyActivity(activityList);
// 处理诊疗执行 // 处理诊疗执行
this.exeActivity(actUseExeList, exeDate); this.exeActivity(actUseExeList, exeDate);
// 检查类医嘱执行后,状态改为"待接收"PENDING_RECEIVE=11
List<Long> actReqIds = activityList.stream().map(AdviceExecuteDetailParam::getRequestId).toList();
List<ServiceRequest> executedReqs = serviceRequestService.listByIds(actReqIds);
List<Long> 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[]{"医嘱执行"})); return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱执行"}));

View File

@@ -47,10 +47,19 @@
) THEN 5 ) THEN 5
WHEN EXISTS ( WHEN EXISTS (
SELECT 1 FROM wor_service_request ws 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' 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 ) 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 ( WHEN EXISTS (
SELECT 1 FROM wor_service_request ws SELECT 1 FROM wor_service_request ws
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'

View File

@@ -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 @EnumValue
private final Integer value; private final Integer value;

View File

@@ -39,6 +39,22 @@ public interface IServiceRequestService extends IService<ServiceRequest> {
*/ */
void updateCompleteRequestStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate); void updateCompleteRequestStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
/**
* 更新检查申请状态已校对护士校对检查申请后状态为CHECK_VERIFIED而非COMPLETED
*
* @param serReqIdList 服务请求id列表
* @param practitionerId 校对人
* @param checkDate 校对时间
*/
void updateCheckVerifiedStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
/**
* 更新检查申请状态待接收护士执行检查申请后状态为PENDING_RECEIVE
*
* @param serReqIdList 服务请求id列表
*/
void updatePendingReceiveStatus(List<Long> serReqIdList);
/** /**
* 获取执行过的诊疗数据 * 获取执行过的诊疗数据
* *

View File

@@ -66,6 +66,31 @@ public class ServiceRequestServiceImpl extends ServiceImpl<ServiceRequestMapper,
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode())); .eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
} }
/**
* 更新检查申请状态已校对护士校对检查申请后状态为CHECK_VERIFIED而非COMPLETED
*
* @param serReqIdList 服务请求id列表
*/
@Override
public void updateCheckVerifiedStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate) {
baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.CHECK_VERIFIED.getValue())
.setPerformerCheckId(SecurityUtils.getLoginUser().getPractitionerId()).setCheckTime(DateUtils.getNowDate()),
new LambdaUpdateWrapper<ServiceRequest>().in(ServiceRequest::getId, serReqIdList)
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
}
/**
* 更新检查申请状态待接收护士执行检查申请后状态为PENDING_RECEIVE
*
* @param serReqIdList 服务请求id列表
*/
@Override
public void updatePendingReceiveStatus(List<Long> serReqIdList) {
baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.PENDING_RECEIVE.getValue()),
new LambdaUpdateWrapper<ServiceRequest>().in(ServiceRequest::getId, serReqIdList)
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
}
/** /**
* 获取执行过的诊疗数据 * 获取执行过的诊疗数据
* *

View File

@@ -34,6 +34,12 @@ export const RequestStatus = {
ENDED: 7, ENDED: 7,
/** 未知 */ /** 未知 */
UNKNOWN: 9, UNKNOWN: 9,
/** 已校对(检查申请:护士校对通过) */
CHECK_VERIFIED: 10,
/** 待接收(检查申请:等待医技科室接单) */
PENDING_RECEIVE: 11,
/** 已接收(检查申请:医技科室已接单) */
CHECK_RECEIVED: 12,
}; };
/** /**
@@ -48,6 +54,9 @@ export const RequestStatusDescriptions = {
6: '停嘱', 6: '停嘱',
7: '不执行', 7: '不执行',
9: '未知', 9: '未知',
10: '已校对',
11: '待接收',
12: '已接收',
}; };
/** /**

View File

@@ -25,9 +25,9 @@ export function getAdviceBaseInfo(queryParams) {
/** /**
* 保存处方(单条) * 保存处方(单条)
* @param {Object} data - 处方数据 * @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({ return request({
url: '/doctor-station/advice/save-advice', url: '/doctor-station/advice/save-advice',
method: 'post', method: 'post',

View File

@@ -1174,7 +1174,9 @@ function handleSaveSign(row, index) {
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo; 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) => { savePrescription({ adviceSaveList: [cleanRow] }, adviceOpType).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功'); proxy.$modal.msgSuccess('保存成功');

View File

@@ -539,7 +539,7 @@ const submit = () => {
requestFormId: requestFormId, requestFormId: requestFormId,
name: selectedNames, name: selectedNames,
descJson: JSON.stringify(submitForm), descJson: JSON.stringify(submitForm),
categoryEnum: '22', categoryEnum: '23',
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success(res.msg || (props.isEditMode ? '修改成功' : '保存成功')); ElMessage.success(res.msg || (props.isEditMode ? '修改成功' : '保存成功'));

View File

@@ -230,6 +230,8 @@
待保存 待保存
</el-tag> </el-tag>
<el-tag v-else-if="scope.row.statusEnum == 1" type="primary">待签发</el-tag> <el-tag v-else-if="scope.row.statusEnum == 1" type="primary">待签发</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 10" type="primary">已校对</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 11" type="primary">待接收</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 3" type="success">已完成</el-tag> <el-tag v-else-if="scope.row.statusEnum == 3" type="success">已完成</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 6" type="error">停止</el-tag> <el-tag v-else-if="scope.row.statusEnum == 6" type="error">停止</el-tag>
<el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag> <el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag>

View File

@@ -1519,13 +1519,14 @@ function handleMedicalAdvice(row) {
} }
// 🔧 每次打开临时医嘱都重新拉取最新数据,确保计费弹窗签发后数据自动更新 // 🔧 每次打开临时医嘱都重新拉取最新数据,确保计费弹窗签发后数据自动更新
// 先清空旧数据 // 🔧 修复 Bug #446: 先保存旧数据再清空,避免竟态条件
const prevAdvices = [...temporaryAdvices.value]
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
temporaryAdvices.value = [] temporaryAdvices.value = []
// 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId保留签名状态 // 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId保留签名状态
const hasSubmittedAdvices = temporaryAdvices.value.length > 0 && const hasSubmittedAdvices = prevAdvices.length > 0 &&
temporaryAdvices.value[0]?.originalMedicine?.encounterId === row.visitId && prevAdvices[0]?.originalMedicine?.encounterId === row.visitId &&
temporaryAdvices.value.some(a => a.originalMedicine?.requestId); prevAdvices.some(a => a.originalMedicine?.requestId);
temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置 temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置
temporaryMedicalLoading.value = true // 🔧 新增:开始加载 temporaryMedicalLoading.value = true // 🔧 新增:开始加载
@@ -1585,7 +1586,13 @@ function handleMedicalAdvice(row) {
adviceType: item.adviceType || contentData.adviceType || item.advice_type || null, adviceType: item.adviceType || contentData.adviceType || item.advice_type || null,
chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null, chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null,
definitionId: item.definitionId || contentData.definitionId || item.definition_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) { } catch (e) {
// 如果解析失败,使用顶层数据 - 兼容 snake_case 和 camelCase 以及后端不同字段名 // 如果解析失败,使用顶层数据 - 兼容 snake_case 和 camelCase 以及后端不同字段名
@@ -1606,7 +1613,13 @@ function handleMedicalAdvice(row) {
adviceType: item.adviceType || item.advice_type || null, adviceType: item.adviceType || item.advice_type || null,
chargeItemId: item.chargeItemId || item.charge_item_id || null, chargeItemId: item.chargeItemId || item.charge_item_id || null,
definitionId: item.definitionId || item.definition_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以便下次提交时执行更新操作 // 🔧 修复:提交成功后,更新 temporaryAdvices 中的 requestId以便下次提交时执行更新操作
function handleTemporaryMedicalSubmit(data) { function handleTemporaryMedicalSubmit(data) {
// 🔧 修复:使用用户修改后的数据,而不是重新加载数据 // 🔧 Bug #445 修复:提交成功后重新拉取数据,确保"待生成"列表正确更新
// 这样可以确保用户修改的内容(如剂量)在保存后仍然正确显示 if (data.patientInfo && data.patientInfo.visitId) {
if (data.temporaryAdvices && data.temporaryAdvices.length > 0) { const row = { visitId: data.patientInfo.visitId, operCode: data.patientInfo.operCode }
// 🔧 关键修复:更新 temporaryAdvices 中的数据,保留用户的修改 temporarySigned.value = true
// 但是需要从后端返回的数据中获取 requestId以便下次提交时执行更新操作 ElMessage.success('临时医嘱已生成(已签发)')
// 如果后端返回了医嘱IDrequestId我们需要更新到 temporaryAdvices 中
temporaryAdvices.value = data.temporaryAdvices.map((advice, index) => { // 重新拉取最新数据,后端已将 statusEnum 从 1(草稿) 更新为 2(已签发)
const originalMedicine = advice.originalMedicine || {} getPrescriptionList(row.visitId, 6, row.operCode).then((res) => {
if (res.code === 200 && res.data) {
// 🔧 关键修复:从后端返回的数据中获取 requestId如果有 const seenIds = new Set()
// 后端返回的医嘱ID可能需要通过某个字段获取这里暂时假设后端会返回 const filteredItems = res.data.filter(item => {
// 如果后端返回的 response 中包含医嘱ID需要更新到 originalMedicine 中 if (item.encounterId !== row.visitId) return false
// 这里暂时保留原逻辑,等待用户提供后端返回的具体数据结构 const at = Number(item.adviceType ?? item.advice_type)
if (at !== 1 && at !== 2) return false
return { const medicineName = item.adviceName || item.advice_name
...advice, if (!medicineName || medicineName.trim() === '') return false
// 确保 originalMedicine 包含所有必要字段 const excludedKeywords = ['术', '超声', '多普勒', '检查', '检验', '彩超', 'X线', 'CT', 'MRI', '扫描', '造影']
originalMedicine: { if (excludedKeywords.some(kw => medicineName.includes(kw))) return false
...originalMedicine, const itemId = item.requestId || item.id
// 保留用户的修改 if (itemId && seenIds.has(itemId)) return false
contentJson: originalMedicine.contentJson || JSON.stringify({ if (itemId) seenIds.add(itemId)
dose: advice.dosage, return true
methodCode: advice.usage, })
rateCode: advice.frequency,
quantity: advice.quantity, const draftItems = filteredItems.filter(item => item.statusEnum === 1)
totalPrice: advice.totalPrice 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 { } else {
// 如果没有传递数据,则清空
temporaryAdvices.value = [] temporaryAdvices.value = []
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
} }
// 🔧 设置签名状态,保持按钮名称一致
temporarySigned.value = true
// 显示成功提示,不关闭弹窗,让用户可以查看已签发的医嘱状态
ElMessage.success('临时医嘱已生成(已签发),可继续查看或修改')
} }
function handleTemporaryMedicalCancel() { function handleTemporaryMedicalCancel() {
// 🔧 修复:用户点击取消时才清空数据,因为用户可能要放弃修改 // 🔧 修复:用户点击取消时才清空数据,因为用户可能要放弃修改
@@ -1911,7 +1985,13 @@ function handleQuoteBilling() {
orgId: contentData.orgId || item.orgId || contentData.positionId || item.positionId || userStore.orgId, orgId: contentData.orgId || item.orgId || contentData.positionId || item.positionId || userStore.orgId,
positionId: contentData.positionId || item.positionId || userStore.orgId, positionId: contentData.positionId || item.positionId || userStore.orgId,
definitionId: contentData.definitionId || item.definitionId, 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) { } catch (e) {
return { return {
@@ -1927,7 +2007,13 @@ function handleQuoteBilling() {
orgId: item.orgId || item.positionId || userStore.orgId, orgId: item.orgId || item.positionId || userStore.orgId,
positionId: item.positionId || userStore.orgId, positionId: item.positionId || userStore.orgId,
definitionId: item.definitionId, 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
} }
} }
}) })

View File

@@ -317,10 +317,11 @@ const getMethodCodeDict = computed(() => {
return dict return dict
}) })
// 🔧 修复 Bug #446: 检查计费药品是否已全部提交(有 requestId用于区分"首次签名"和"已提交重开" // 🔧 检查是否已全部签发statusEnum=2用于控制"一键签名"按钮是否禁用
// 注意:不能依赖 requestId因为草稿记录也有 requestId
const allItemsSubmitted = computed(() => { const allItemsSubmitted = computed(() => {
const meds = props.billingMedicines || [] 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 // 初始化 displayAdvices
const initDisplayAdvices = () => { const initDisplayAdvices = () => {
// 只要有用户修改过的数据,就优先使用用户修改后的数据 // 区域二只显示已生成(已签发)的数据,没有时保持为空,不自动转换区域一的草稿
// 避免自动转换覆盖用户的修改
if (props.temporaryAdvices && props.temporaryAdvices.length > 0) { if (props.temporaryAdvices && props.temporaryAdvices.length > 0) {
// 🔧 修复:将旧编码映射到新编码
displayAdvices.value = props.temporaryAdvices.map(mapUsageCode) displayAdvices.value = props.temporaryAdvices.map(mapUsageCode)
} else { } else {
// 否则自动转换第一区域的已引用计费药品 displayAdvices.value = []
displayAdvices.value = convertedAdvices.value
} }
} }
// 初始化 // 初始化
initDisplayAdvices() initDisplayAdvices()
// 组件挂载时,如果没有传入临时医嘱数据,将自动转换后的数据同步到父组件
// 确保修改可以被父组件保存,下次打开仍然能看到
onMounted(() => { onMounted(() => {
// 初始化 displayAdvices
initDisplayAdvices() 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 // 🔧 新增:监听 temporary-advices prop 的变化,同步更新 displayAdvices
@@ -465,23 +454,9 @@ watch(() => props.temporaryAdvices, (newVal, oldVal) => {
} }
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
// 监听第一区域的计费药品变化,如果第一区域更新了,并且用户还没修改过第二区域数据,自动更新第二区域 // 🔧 修复:不再自动把区域一草稿转为区域二数据,区域二只显示已签发的记录
watch(() => props.billingMedicines, (newVal) => { watch(() => props.billingMedicines, (newVal) => {
console.log('=== watch billingMedicines 被调用 ===') // 空 watcher仅保留结构避免破坏其他逻辑
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 已有数据 ===')
}
}, { deep: true }) }, { 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('没有可保存的医嘱数据') ElMessage.warning('没有可保存的医嘱数据')
return return
} }
@@ -709,7 +686,7 @@ const handleSubmit = async () => {
// 构建保存医嘱的请求参数 // 构建保存医嘱的请求参数
const saveData = { const saveData = {
organizationId: props.patientInfo.orgId || props.patientInfo.organizationId || 1, // 使用计费时的orgId organizationId: props.patientInfo.orgId || props.patientInfo.organizationId || 1, // 使用计费时的orgId
adviceSaveList: displayAdvices.value.map((advice, index) => { adviceSaveList: itemsToSign.map((advice, index) => {
// 获取原始药品数据 // 获取原始药品数据
const originalMedicine = advice.originalMedicine const originalMedicine = advice.originalMedicine
@@ -764,53 +741,44 @@ const handleSubmit = async () => {
// 重新序列化contentJson // 重新序列化contentJson
const updatedContentJson = JSON.stringify(contentJsonData); const updatedContentJson = JSON.stringify(contentJsonData);
// 构造请求参数(与门诊医生工作站完全一致 // 🔧 构造请求参数:与计费弹窗 handleSave 保持完全一致的模式
// 先展开原始 contentJson 的所有字段,然后用当前值覆盖(保证不丢字段)
return { return {
...contentJsonData,
// 基础信息 // 基础信息
// 🔧 修复dbOpType 的判断逻辑 - 如果有 requestId 则为修改,否则为新增
// 但对于从计费药品转换来的数据,即使没有 requestId也应该先更新 chargeItemId
dbOpType: originalMedicine?.requestId ? '2' : (originalMedicine?.chargeItemId ? '2' : '1'), dbOpType: originalMedicine?.requestId ? '2' : (originalMedicine?.chargeItemId ? '2' : '1'),
adviceType: originalMedicine?.adviceType || 1, // 使用原始类型或默认1 adviceType: originalMedicine?.adviceType || 1,
requestId: originalMedicine?.requestId, requestId: originalMedicine?.requestId,
chargeItemId: originalMedicine?.chargeItemId, chargeItemId: originalMedicine?.chargeItemId,
contentJson: updatedContentJson, contentJson: updatedContentJson,
categoryCode: contentJsonData.categoryCode || originalMedicine?.categoryCode, categoryCode: contentJsonData.categoryCode || originalMedicine?.categoryCode,
pharmacologyCategoryCode: contentJsonData.pharmacologyCategoryCode || originalMedicine?.pharmacologyCategoryCode,
partPercent: originalMedicine?.partPercent || contentJsonData.partPercent || 1, partPercent: originalMedicine?.partPercent || contentJsonData.partPercent || 1,
partAttributeEnum: originalMedicine?.partAttributeEnum || contentJsonData.partAttributeEnum,
executeNum: contentJsonData.executeNum || originalMedicine?.executeNum || 1, executeNum: contentJsonData.executeNum || originalMedicine?.executeNum || 1,
prescriptionNo: originalMedicine?.prescriptionNo,
// 数量和单位:使用重新计算后的数量 // 数量和单位:使用重新计算后的数量
quantity: quantity, quantity: quantity,
dispensePerDuration: originalMedicine?.dispensePerDuration || contentJsonData.dispensePerDuration,
unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit, unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit,
unitPrice: unitPrice, unitPrice: unitPrice,
// 总价根据新的数量重新计算
totalPrice: totalPrice, totalPrice: totalPrice,
definitionId: originalMedicine?.definitionId ? String(originalMedicine.definitionId) : contentJsonData.definitionId ? String(contentJsonData.definitionId) : advice.definitionId ? String(advice.definitionId) : null, 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, definitionDetailId: originalMedicine?.definitionDetailId ? String(originalMedicine.definitionDetailId) : contentJsonData.definitionDetailId ? String(contentJsonData.definitionDetailId) : advice.definitionDetailId ? String(advice.definitionDetailId) : null,
lotNumber: originalMedicine?.lotNumber || contentJsonData.lotNumber, lotNumber: originalMedicine?.lotNumber || contentJsonData.lotNumber,
// 状态和类型 // 状态和类型
statusEnum: originalMedicine?.statusEnum || 2, // 默认状态:已发送
categoryEnum: originalMedicine?.categoryEnum || contentJsonData.categoryEnum || 1, categoryEnum: originalMedicine?.categoryEnum || contentJsonData.categoryEnum || 1,
// 药品/诊疗信息 - 🔧 修复优先使用originalMedicine中的adviceDefinitionId和adviceTableName // 药品/诊疗信息
// 🔧 关键修复确保adviceDefinitionId不为null使用definitionId作为后备 adviceDefinitionId: originalMedicine?.adviceDefinitionId || contentJsonData.adviceDefinitionId || advice.definitionId || contentJsonData.definitionId,
adviceDefinitionId: originalMedicine?.adviceDefinitionId || contentJsonData.adviceDefinitionId || advice.definitionId || contentJsonData.definitionId || definitionId,
adviceTableName: originalMedicine?.adviceTableName || contentJsonData.adviceTableName || 'med_medication_definition', adviceTableName: originalMedicine?.adviceTableName || contentJsonData.adviceTableName || 'med_medication_definition',
adviceName: advice.adviceName, adviceName: advice.adviceName,
minUnitQuantity: originalMedicine?.minUnitQuantity || contentJsonData.minUnitQuantity,
// 患者和就诊信息 // 患者和就诊信息
patientId: props.patientInfo.patientId, 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, locationId: originalMedicine?.positionId || contentJsonData.locationId || props.patientInfo.orgId || props.patientInfo.positionId,
positionId: originalMedicine?.positionId || contentJsonData.positionId, positionId: originalMedicine?.positionId || contentJsonData.positionId,
orgId: originalMedicine?.orgId || props.patientInfo.orgId || props.patientInfo.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, encounterId: props.patientInfo.visitId || contentJsonData.encounterId || originalMedicine?.encounterId,
accountId: contentJsonData.accountId || originalMedicine?.accountId, accountId: contentJsonData.accountId || originalMedicine?.accountId,
conditionId: contentJsonData.conditionId || originalMedicine?.conditionId, conditionId: contentJsonData.conditionId || originalMedicine?.conditionId,
@@ -818,12 +786,10 @@ const handleSubmit = async () => {
conditionDefinitionId: originalMedicine?.conditionDefinitionId || contentJsonData.conditionDefinitionId, conditionDefinitionId: originalMedicine?.conditionDefinitionId || contentJsonData.conditionDefinitionId,
// 治疗信息 // 治疗信息
therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1, // 默认临时医嘱 therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1,
// 🔧 修复:methodCode 使用编码,而不是中文名称 methodCode: methodCode,
methodCode: methodCode, // 用法(使用编码) rateCode: advice.frequency,
rateCode: advice.frequency, // 频次
dose: advice.dosage, dose: advice.dosage,
firstDose: originalMedicine?.firstDose || contentJsonData.firstDose,
doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit, doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit,
skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag, skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag,
injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag, injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag,
@@ -832,55 +798,30 @@ const handleSubmit = async () => {
groupId: originalMedicine?.groupId, groupId: originalMedicine?.groupId,
packageId: originalMedicine?.packageId || contentJsonData.packageId, packageId: originalMedicine?.packageId || contentJsonData.packageId,
// 诊疗活动信息
activityId: contentJsonData.adviceDefinitionId || originalMedicine?.adviceDefinitionId, // 对于诊疗类型,使用 adviceDefinitionId 作为 activity_id
// 医保信息 // 医保信息
ybClassEnum: originalMedicine?.ybClassEnum || contentJsonData.ybClassEnum, ybClassEnum: originalMedicine?.ybClassEnum || contentJsonData.ybClassEnum,
// 中药信息 // 🔧 补充:手术计费上下文
chineseHerbsDoseQuantity: originalMedicine?.chineseHerbsDoseQuantity || contentJsonData.chineseHerbsDoseQuantity || 1 generateSourceEnum: 6,
sourceBillNo: props.patientInfo?.operCode || originalMedicine?.sourceBillNo || ''
} }
}) })
} }
// 调用保存医嘱接口 // 🔧 修复:使用 savePrescription + adviceOpType='2'(后端 AdviceOpType: '2'=SIGN_ADVICE 签发)
// 如果已签名,则执行签发操作('1'),否则执行保存操作('0' const response = await savePrescription(saveData, '2')
const adviceOpType = isSigned.value ? '1' : '0'
const response = await savePrescription(saveData, adviceOpType)
if (response.code === 200) { if (response.code === 200) {
ElMessage.success('临时医嘱保存成功') ElMessage.success('临时医嘱保存成功')
// 🔧 关键修复:处理后端返回的医嘱 ID // 🔧 签名成功,后端已更新已有记录的 statusEnum 为 2
// 后端返回的 data 数组只包含新创建的医嘱记录的 ID不包含已更新的记录 // 不需要手动更新 requestId签名操作是 UPDATE不是 INSERT后端返回新 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++
}
}
})
}
// 🔧 修复:将保存后的医嘱数据(包含用户的修改和后端返回的 requestId传递给父组件 // 🔧 修复:将保存后的医嘱数据传递给父组件
// 这样父组件可以使用用户修改后的数据,而不是重新加载数据
const submitData = { const submitData = {
patientInfo: props.patientInfo, patientInfo: props.patientInfo,
billingMedicines: props.billingMedicines, billingMedicines: props.billingMedicines,
temporaryAdvices: displayAdvices.value, // 使用用户修改后的数据,包含后端返回的 requestId temporaryAdvices: itemsToSign,
signature: { signature: {
doctorName: currentUser.value.name, doctorName: currentUser.value.name,
signatureTime: signatureTime.value signatureTime: signatureTime.value