fix(#643): 门诊手术安排-术中医嘱删除改为状态回退,修复刷新后医嘱重现
- 前端:删除操作改为 UPDATE 状态回退(statusEnum ACTIVE→DRAFT),清除签发人/签发时间 - 后端:回退时跳过发放/计费/绑耗逻辑,清除 signCode,回退 chargeItem 状态为 DRAFT - 后端:回退时保持原始 generateSourceEnum,避免刷新查询不到记录 - 安全:回退前校验 encounterId 所有权,防止跨就诊 IDOR
This commit is contained in:
@@ -1189,6 +1189,28 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
if (medicationRequest.getId() == null) {
|
||||
firstTimeSave = true;
|
||||
}
|
||||
// 🔧 Bug #643: 检测是否为状态回退操作(已签发 → 待签发)
|
||||
// 当 is_save=true 且已有记录的 statusEnum 为 ACTIVE(2) 时,
|
||||
// 说明前端请求将已签发医嘱回退到待签发状态,需要清除签发信息
|
||||
boolean isRollback = false;
|
||||
if (is_save && !firstTimeSave) {
|
||||
MedicationRequest existing = iMedicationRequestService.getById(medicationRequest.getId());
|
||||
if (existing != null) {
|
||||
// 所有权校验:确保操作的医嘱属于当前就诊
|
||||
if (!existing.getEncounterId().equals(adviceSaveDto.getEncounterId())) {
|
||||
log.error("Bug#643: 越权操作,医嘱 encounterId={} 与请求 encounterId={} 不匹配",
|
||||
existing.getEncounterId(), adviceSaveDto.getEncounterId());
|
||||
throw new ServiceException("无权操作此医嘱");
|
||||
}
|
||||
if (RequestStatus.ACTIVE.getValue().equals(existing.getStatusEnum())) {
|
||||
isRollback = true;
|
||||
// 保持原始 generateSourceEnum,避免被默认值覆盖导致刷新查询不到
|
||||
medicationRequest.setGenerateSourceEnum(existing.getGenerateSourceEnum());
|
||||
log.info("Bug#643: 检测到状态回退操作, requestId={}, statusEnum {} → {}",
|
||||
medicationRequest.getId(), existing.getStatusEnum(), RequestStatus.DRAFT.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 确保 contentJson 包含 remark
|
||||
if (adviceSaveDto.getRemark() != null && !adviceSaveDto.getRemark().isEmpty()) {
|
||||
medicationRequest.setContentJson(injectRemarkIntoContentJson(medicationRequest.getContentJson(), adviceSaveDto.getRemark()));
|
||||
@@ -1197,7 +1219,23 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
if (firstTimeSave) {
|
||||
medRequestIdList.add(medicationRequest.getId().toString());
|
||||
}
|
||||
if (is_save) {
|
||||
// 🔧 Bug #643: 回退操作跳过发放/计费/绑耗逻辑,只清除签发信息
|
||||
if (isRollback) {
|
||||
// 清除 medication_request.sign_code
|
||||
UpdateWrapper<MedicationRequest> signClearWrapper = new UpdateWrapper<>();
|
||||
signClearWrapper.eq("id", medicationRequest.getId());
|
||||
signClearWrapper.set("sign_code", null);
|
||||
iMedicationRequestService.update(null, signClearWrapper);
|
||||
log.info("Bug#643: 已清除 signCode, requestId={}", medicationRequest.getId());
|
||||
|
||||
// 回退 adm_charge_item.status_enum 从 BILLABLE(2) 到 DRAFT(0)
|
||||
UpdateWrapper<ChargeItem> chargeRollbackWrapper = new UpdateWrapper<>();
|
||||
chargeRollbackWrapper.eq("service_id", medicationRequest.getId());
|
||||
chargeRollbackWrapper.eq("service_table", CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||
chargeRollbackWrapper.set("status_enum", ChargeItemStatus.DRAFT.getValue());
|
||||
iChargeItemService.update(null, chargeRollbackWrapper);
|
||||
log.info("Bug#643: 已回退费用项状态为 DRAFT, requestId={}", medicationRequest.getId());
|
||||
} else if (is_save) {
|
||||
// 处理药品发放
|
||||
Long dispenseId = iMedicationDispenseService.handleMedicationDispense(medicationRequest,
|
||||
adviceSaveDto.getDbOpType());
|
||||
@@ -1658,8 +1696,40 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
if (adviceSaveDto.getRemark() != null && !adviceSaveDto.getRemark().isEmpty()) {
|
||||
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), adviceSaveDto.getRemark()));
|
||||
}
|
||||
// 🔧 Bug #643: 检测是否为耗材状态回退操作(已签发 → 待签发)
|
||||
boolean deviceFirstTimeSave = (deviceRequest.getId() == null);
|
||||
boolean deviceIsRollback = false;
|
||||
if (is_save && !deviceFirstTimeSave) {
|
||||
DeviceRequest existingDevice = iDeviceRequestService.getById(deviceRequest.getId());
|
||||
if (existingDevice != null) {
|
||||
// 所有权校验
|
||||
if (!existingDevice.getEncounterId().equals(adviceSaveDto.getEncounterId())) {
|
||||
log.error("Bug#643: 越权操作(耗材),医嘱 encounterId={} 与请求 encounterId={} 不匹配",
|
||||
existingDevice.getEncounterId(), adviceSaveDto.getEncounterId());
|
||||
throw new ServiceException("无权操作此医嘱");
|
||||
}
|
||||
if (RequestStatus.ACTIVE.getValue().equals(existingDevice.getStatusEnum())) {
|
||||
deviceIsRollback = true;
|
||||
// 保持原始 generateSourceEnum,避免被默认值覆盖导致刷新查询不到
|
||||
deviceRequest.setGenerateSourceEnum(existingDevice.getGenerateSourceEnum());
|
||||
log.info("Bug#643: 检测到耗材状态回退操作, requestId={}, statusEnum {} → {}",
|
||||
deviceRequest.getId(), existingDevice.getStatusEnum(), RequestStatus.DRAFT.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||
if (is_save) {
|
||||
// 🔧 Bug #643: 耗材回退操作跳过发放/计费逻辑,只清除签发信息
|
||||
if (deviceIsRollback) {
|
||||
// 清除 wor_device_request.sign_code(如果该表有此字段)
|
||||
// 回退 adm_charge_item.status_enum 从 BILLABLE(2) 到 DRAFT(0)
|
||||
UpdateWrapper<ChargeItem> deviceChargeRollbackWrapper = new UpdateWrapper<>();
|
||||
deviceChargeRollbackWrapper.eq("service_id", deviceRequest.getId());
|
||||
deviceChargeRollbackWrapper.eq("service_table", CommonConstants.TableName.WOR_DEVICE_REQUEST);
|
||||
deviceChargeRollbackWrapper.set("status_enum", ChargeItemStatus.DRAFT.getValue());
|
||||
iChargeItemService.update(null, deviceChargeRollbackWrapper);
|
||||
log.info("Bug#643: 已回退耗材费用项状态为 DRAFT, requestId={}", deviceRequest.getId());
|
||||
} else if (is_save) {
|
||||
// 处理耗材发放
|
||||
Long dispenseId = iDeviceDispenseService.handleDeviceDispense(deviceRequest,
|
||||
adviceSaveDto.getDbOpType());
|
||||
|
||||
@@ -1632,7 +1632,7 @@
|
||||
v-else
|
||||
v-model:temporary-advices="temporaryAdvices"
|
||||
:patient-info="temporaryPatientInfo"
|
||||
:billing-medicines="temporaryBillingMedicines"
|
||||
v-model:billing-medicines="temporaryBillingMedicines"
|
||||
:is-signed-prop="temporarySigned"
|
||||
@submit="handleTemporaryMedicalSubmit"
|
||||
@cancel="handleTemporaryMedicalCancel"
|
||||
|
||||
@@ -447,7 +447,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
// 定义emit事件
|
||||
const emit = defineEmits(['submit', 'cancel', 'refresh', 'quote-billing', 'update:temporary-advices'])
|
||||
const emit = defineEmits(['submit', 'cancel', 'refresh', 'quote-billing', 'update:temporary-advices', 'update:billing-medicines'])
|
||||
|
||||
// 用户store
|
||||
const userStore = useUserStore()
|
||||
@@ -917,22 +917,114 @@ const handleSignAndSubmit = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteAdvice = (index) => {
|
||||
ElMessageBox.confirm('确定要删除这条医嘱吗?', '提示', {
|
||||
/**
|
||||
* 回退已签发医嘱到待签发状态
|
||||
* - 不是物理删除,而是将 statusEnum 从 2(ACTIVE) 回退到 1(DRAFT)
|
||||
* - 清除签发人(signDoctorName)和签发时间(signDate)
|
||||
* - 回退后刷新,该医嘱将从"已生成"移到"待生成"列表
|
||||
*/
|
||||
const handleDeleteAdvice = async (index) => {
|
||||
ElMessageBox.confirm('确定要回退这条医嘱吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 构建新的临时医嘱数据
|
||||
}).then(async () => {
|
||||
const rollbackAdvice = displayAdvices.value[index]
|
||||
if (!rollbackAdvice) return
|
||||
|
||||
const originalMedicine = rollbackAdvice.originalMedicine
|
||||
const originalList = [...displayAdvices.value]
|
||||
const updatedAdvices = [...displayAdvices.value]
|
||||
updatedAdvices.splice(index, 1)
|
||||
|
||||
// 通知父组件更新数据
|
||||
emit('update:temporary-advices', updatedAdvices)
|
||||
|
||||
ElMessage.success('删除成功')
|
||||
if (originalMedicine && originalMedicine.requestId) {
|
||||
try {
|
||||
// 清除 contentJson 中的签发人和签发时间
|
||||
let contentData = {}
|
||||
try {
|
||||
contentData = typeof originalMedicine.contentJson === 'string'
|
||||
? JSON.parse(originalMedicine.contentJson)
|
||||
: (originalMedicine.contentJson || {})
|
||||
} catch (e) { /* ignore */ }
|
||||
contentData.signDoctorName = ''
|
||||
contentData.signDate = ''
|
||||
|
||||
const updateItem = {
|
||||
requestId: originalMedicine.requestId,
|
||||
dbOpType: '2', // UPDATE(非 DELETE)
|
||||
adviceType: originalMedicine.adviceType || 1,
|
||||
encounterId: originalMedicine.encounterId || props.patientInfo.visitId,
|
||||
patientId: originalMedicine.patientId || props.patientInfo.patientId,
|
||||
contentJson: JSON.stringify(contentData),
|
||||
}
|
||||
// adviceOpType='1'(SAVE) → 后端会设置 statusEnum=DRAFT,完成状态回退
|
||||
const res = await savePrescription(
|
||||
{ organizationId: props.patientInfo.orgId || props.patientInfo.organizationId || 1, adviceSaveList: [updateItem] },
|
||||
'1'
|
||||
)
|
||||
if (res.code === 200) {
|
||||
// 回退成功:将药品放回"待生成"列表(保留 requestId,只是状态变了)
|
||||
if (originalMedicine) {
|
||||
const restoredItem = {
|
||||
medicineName: originalMedicine.medicineName || contentData.adviceName || originalMedicine.adviceName || '',
|
||||
specification: originalMedicine.specification || contentData.volume || '',
|
||||
quantity: originalMedicine.quantity || contentData.quantity || 1,
|
||||
batchNumber: originalMedicine.lotNumber || contentData.lotNumber || '',
|
||||
unitPrice: originalMedicine.unitPrice || contentData.unitPrice || 0,
|
||||
subtotal: originalMedicine.totalPrice || contentData.totalPrice || 0,
|
||||
insuranceType: contentData.insuranceType === 1 || originalMedicine.insuranceType === 1 ? '医保' : '自费',
|
||||
orgId: originalMedicine.orgId || contentData.orgId || props.patientInfo.orgId,
|
||||
positionId: originalMedicine.positionId || contentData.positionId || props.patientInfo.orgId,
|
||||
adviceDefinitionId: originalMedicine.adviceDefinitionId || contentData.adviceDefinitionId || null,
|
||||
adviceTableName: originalMedicine.adviceTableName || contentData.adviceTableName || null,
|
||||
adviceType: originalMedicine.adviceType || contentData.adviceType || 1,
|
||||
definitionId: originalMedicine.definitionId || contentData.definitionId || null,
|
||||
definitionDetailId: originalMedicine.definitionDetailId || contentData.definitionDetailId || null,
|
||||
requestId: originalMedicine.requestId, // 保留 requestId,记录仍在数据库
|
||||
chargeItemId: originalMedicine.chargeItemId || null,
|
||||
contentJson: JSON.stringify(contentData), // 已清除签名字段
|
||||
_signed: false,
|
||||
}
|
||||
emit('update:billing-medicines', [...props.billingMedicines, restoredItem])
|
||||
}
|
||||
ElMessage.success('回退成功')
|
||||
} else {
|
||||
emit('update:temporary-advices', originalList)
|
||||
ElMessage.error(res.msg || '回退失败,请重试')
|
||||
}
|
||||
} catch (e) {
|
||||
emit('update:temporary-advices', originalList)
|
||||
ElMessage.error('回退失败,请重试')
|
||||
}
|
||||
} else {
|
||||
// 未持久化到数据库的医嘱(无 requestId),本地移除即可
|
||||
if (originalMedicine) {
|
||||
const restoredItem = {
|
||||
medicineName: originalMedicine.medicineName || originalMedicine.adviceName || '',
|
||||
specification: originalMedicine.specification || '',
|
||||
quantity: originalMedicine.quantity || 1,
|
||||
unitPrice: originalMedicine.unitPrice || 0,
|
||||
subtotal: originalMedicine.totalPrice || 0,
|
||||
insuranceType: originalMedicine.insuranceType === 1 ? '医保' : '自费',
|
||||
orgId: originalMedicine.orgId || props.patientInfo.orgId,
|
||||
positionId: originalMedicine.positionId || props.patientInfo.orgId,
|
||||
adviceDefinitionId: originalMedicine.adviceDefinitionId || null,
|
||||
adviceTableName: originalMedicine.adviceTableName || null,
|
||||
adviceType: originalMedicine.adviceType || 1,
|
||||
definitionId: originalMedicine.definitionId || null,
|
||||
definitionDetailId: originalMedicine.definitionDetailId || null,
|
||||
requestId: null,
|
||||
chargeItemId: null,
|
||||
contentJson: originalMedicine.contentJson || null,
|
||||
_signed: false,
|
||||
}
|
||||
emit('update:billing-medicines', [...props.billingMedicines, restoredItem])
|
||||
}
|
||||
ElMessage.success('回退成功')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user