Fix Bug #437: 【门诊手术计费】保存签章TOCTOU竞态致重复提交,且耗材计费项目缺失/重复、手术单号未关联
Fix: 频次总量计算改用字典store动态读取,el-input-number新增@input实时计算
This commit is contained in:
@@ -107,8 +107,4 @@ public class OpScheduleDto extends OpSchedule {
|
|||||||
*/
|
*/
|
||||||
private String createByName;
|
private String createByName;
|
||||||
|
|
||||||
/**
|
|
||||||
* 费用类别
|
|
||||||
*/
|
|
||||||
private String feeType;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1529,6 +1529,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
|
||||||
}
|
}
|
||||||
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
|
deviceRequest.setPrescriptionNo(adviceSaveDto.getSourceBillNo()); // 来源业务单据号(手术单号)
|
||||||
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||||
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||||
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
|
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
|
||||||
@@ -1835,6 +1836,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
||||||
}
|
}
|
||||||
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
|
serviceRequest.setPrescriptionNo(adviceSaveDto.getSourceBillNo()); // 来源业务单据号(手术单号)
|
||||||
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
|
||||||
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
|
||||||
|
|
||||||
@@ -2030,10 +2032,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
||||||
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
||||||
sourceEnum, sourceBillNo);
|
sourceEnum, sourceBillNo);
|
||||||
// 手术计费场景:sourceBillNo 不为空时,只保留诊疗请求(3/6),过滤掉药品(1)和耗材(2)
|
// 手术计费场景:sourceBillNo 不为空时,过滤掉药品(1),保留耗材(2)和诊疗(3/6)
|
||||||
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
|
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
|
||||||
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
|
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
|
||||||
&& (dto.getAdviceType() == 1 || dto.getAdviceType() == 2));
|
&& dto.getAdviceType() == 1);
|
||||||
}
|
}
|
||||||
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
||||||
// 请求状态
|
// 请求状态
|
||||||
|
|||||||
@@ -539,7 +539,8 @@
|
|||||||
AND T1.refund_medicine_id IS NULL
|
AND T1.refund_medicine_id IS NULL
|
||||||
ORDER BY T1.status_enum,T1.sort_number)
|
ORDER BY T1.status_enum,T1.sort_number)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
-- 🔧 新增:查询门诊术中计费生成的耗材数据(这些数据存在于 adm_charge_item 和 wor_device_request)
|
-- 🔧 查询仅存在于 adm_charge_item 的"孤儿"耗材数据(DeviceRequest 缺失或 generate_source_enum 未设置)
|
||||||
|
-- 正常 DeviceRequest(generate_source_enum 已赋值)由下方 Part 3 统一负责,此处不做重复覆盖避免 UNION ALL 重复行
|
||||||
(SELECT 2 AS advice_type,
|
(SELECT 2 AS advice_type,
|
||||||
CI.service_id AS request_id,
|
CI.service_id AS request_id,
|
||||||
CI.service_id || '-ci-dev' AS unique_key,
|
CI.service_id || '-ci-dev' AS unique_key,
|
||||||
@@ -584,7 +585,7 @@
|
|||||||
WHERE CI.delete_flag = '0'
|
WHERE CI.delete_flag = '0'
|
||||||
AND CI.service_table = 'wor_device_request'
|
AND CI.service_table = 'wor_device_request'
|
||||||
<if test="generateSourceEnum != null">
|
<if test="generateSourceEnum != null">
|
||||||
AND (DR.generate_source_enum IS NULL OR DR.generate_source_enum = #{generateSourceEnum})
|
AND DR.generate_source_enum IS NULL <!-- 仅匹配孤儿记录,normal DeviceRequest 由 Part 3 负责,避免 UNION ALL 重复 -->
|
||||||
</if>
|
</if>
|
||||||
<if test="historyFlag == '0'.toString()">
|
<if test="historyFlag == '0'.toString()">
|
||||||
AND CI.encounter_id = #{encounterId}
|
AND CI.encounter_id = #{encounterId}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
|
||||||
// 日期格式化
|
// 日期格式化
|
||||||
export function parseTime(time, pattern) {
|
export function parseTime(time, pattern) {
|
||||||
if (arguments.length === 0 || !time) {
|
if (arguments.length === 0 || !time) {
|
||||||
@@ -275,30 +277,13 @@ export function blobValidate(data) {
|
|||||||
|
|
||||||
// 按照频次天数计算总数量
|
// 按照频次天数计算总数量
|
||||||
export function calculateQuantityByDays(frequency, days) {
|
export function calculateQuantityByDays(frequency, days) {
|
||||||
// const dict = useDict('rate_code').rate_code.value
|
const dicts = useDictStore().getDict('rate_code');
|
||||||
// const rate = dict.find(item => item.value === frequency).remark
|
if (!dicts) return;
|
||||||
// if(rate){
|
const dict = dicts.find(item => item.value === frequency);
|
||||||
// return Math.floor(Number(rate) * days)
|
if (!dict?.remark) return;
|
||||||
// } else {
|
const rate = Number(dict.remark);
|
||||||
// return undefined
|
if (isNaN(rate) || !rate) return;
|
||||||
// }
|
const quantity = rate * days;
|
||||||
const frequencyMap = {
|
|
||||||
ST: 1,
|
|
||||||
QD: 1, // 每日一次
|
|
||||||
BID: 2, // 每日两次
|
|
||||||
TID: 3, // 每日三次
|
|
||||||
QID: 4, // 每日四次
|
|
||||||
QN: 1, // 每晚一次
|
|
||||||
QOD: 1 / 2, // 每隔一日一次
|
|
||||||
QW: 1 / 7, // 每周一次
|
|
||||||
BIW: 2 / 7, // 每周两次
|
|
||||||
TIW: 3 / 7, // 每周三次
|
|
||||||
QOW: 1 / 14, // 隔周一次
|
|
||||||
};
|
|
||||||
if (!frequencyMap[frequency]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const quantity = frequencyMap[frequency] * days;
|
|
||||||
return quantity < 1 ? 1 : Math.ceil(quantity);
|
return quantity < 1 ? 1 : Math.ceil(quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ watch(
|
|||||||
console.log(prescriptionList.value,"prescriptionList.value")
|
console.log(prescriptionList.value,"prescriptionList.value")
|
||||||
if(newValue&&newValue.length>0){
|
if(newValue&&newValue.length>0){
|
||||||
let saveList = prescriptionList.value.filter((item) => {
|
let saveList = prescriptionList.value.filter((item) => {
|
||||||
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
})
|
})
|
||||||
console.log(saveList,"prescriptionList.value")
|
console.log(saveList,"prescriptionList.value")
|
||||||
if (saveList.length == 0) {
|
if (saveList.length == 0) {
|
||||||
@@ -1015,7 +1015,7 @@ function handleSave() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let saveList = prescriptionList.value.filter((item) => {
|
let saveList = prescriptionList.value.filter((item) => {
|
||||||
return item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
});
|
});
|
||||||
// let saveList = prescriptionList.value
|
// let saveList = prescriptionList.value
|
||||||
// .filter((item) => {
|
// .filter((item) => {
|
||||||
@@ -1080,42 +1080,44 @@ function handleSaveSign(row, index) {
|
|||||||
proxy.$modal.msgWarning('诊疗项目必须选择执行科室');
|
proxy.$modal.msgWarning('诊疗项目必须选择执行科室');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isSaving.value = true; // #437 立即加锁,消除 TOCTOU 竞态
|
||||||
proxy.$refs['formRef' + index].validate((valid) => {
|
proxy.$refs['formRef' + index].validate((valid) => {
|
||||||
if (valid) {
|
if (!valid) {
|
||||||
isSaving.value = true; // #437 加锁
|
isSaving.value = false; // 验证失败释放锁
|
||||||
row.isEdit = false;
|
return;
|
||||||
isAdding.value = false;
|
|
||||||
expandOrder.value = [];
|
|
||||||
row.patientId = props.patientInfo.patientId;
|
|
||||||
row.encounterId = props.patientInfo.encounterId;
|
|
||||||
row.accountId = props.patientInfo.accountId;
|
|
||||||
const cleanRow = JSON.parse(JSON.stringify(row));
|
|
||||||
cleanRow.contentJson = JSON.stringify(cleanRow);
|
|
||||||
cleanRow.dbOpType = cleanRow.requestId ? '2' : '1';
|
|
||||||
cleanRow.minUnitQuantity = cleanRow.quantity * cleanRow.partPercent;
|
|
||||||
cleanRow.categoryEnum = cleanRow.adviceType
|
|
||||||
// 如果是手术计费,设置生成来源和来源业务单据号
|
|
||||||
if (props.patientInfo.sourceBillNo) {
|
|
||||||
cleanRow.generateSourceEnum = 6; // 手术计费
|
|
||||||
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
|
||||||
}
|
|
||||||
console.log('cleanRow', cleanRow)
|
|
||||||
savePrescription({ adviceSaveList: [cleanRow] }).then((res) => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
proxy.$modal.msgSuccess('保存成功');
|
|
||||||
getListInfo(false);
|
|
||||||
nextId.value = 1;
|
|
||||||
// 🔧 Bug Fix #238: 如果诊疗项目缺少执行科室,标记为需要修复的脏数据
|
|
||||||
if (row.adviceType === 3 && !row.orgId) {
|
|
||||||
console.warn('Bug #238: 检测到诊疗项目保存时缺少执行科室,请手动编辑修正:', cleanRow);
|
|
||||||
proxy.$modal.msgWarning('诊疗项目执行科室信息不完整,请编辑后重新保存');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
|
||||||
isSaving.value = false; // #437 释放锁
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
row.isEdit = false;
|
||||||
|
isAdding.value = false;
|
||||||
|
expandOrder.value = [];
|
||||||
|
row.patientId = props.patientInfo.patientId;
|
||||||
|
row.encounterId = props.patientInfo.encounterId;
|
||||||
|
row.accountId = props.patientInfo.accountId;
|
||||||
|
const cleanRow = JSON.parse(JSON.stringify(row));
|
||||||
|
cleanRow.contentJson = JSON.stringify(cleanRow);
|
||||||
|
cleanRow.dbOpType = cleanRow.requestId ? '2' : '1';
|
||||||
|
cleanRow.minUnitQuantity = cleanRow.quantity * cleanRow.partPercent;
|
||||||
|
cleanRow.categoryEnum = cleanRow.adviceType
|
||||||
|
// 如果是手术计费,设置生成来源和来源业务单据号
|
||||||
|
if (props.patientInfo.sourceBillNo) {
|
||||||
|
cleanRow.generateSourceEnum = 6; // 手术计费
|
||||||
|
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
||||||
|
}
|
||||||
|
console.log('cleanRow', cleanRow)
|
||||||
|
savePrescription({ adviceSaveList: [cleanRow] }, '1').then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
proxy.$modal.msgSuccess('保存成功');
|
||||||
|
getListInfo(false);
|
||||||
|
nextId.value = 1;
|
||||||
|
// 🔧 Bug Fix #238: 如果诊疗项目缺少执行科室,标记为需要修复的脏数据
|
||||||
|
if (row.adviceType === 3 && !row.orgId) {
|
||||||
|
console.warn('Bug #238: 检测到诊疗项目保存时缺少执行科室,请手动编辑修正:', cleanRow);
|
||||||
|
proxy.$modal.msgWarning('诊疗项目执行科室信息不完整,请编辑后重新保存');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
isSaving.value = false; // #437 释放锁
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 签退
|
// 签退
|
||||||
|
|||||||
@@ -315,6 +315,7 @@
|
|||||||
data-prop="dispensePerDuration">
|
data-prop="dispensePerDuration">
|
||||||
<el-input-number v-model="scope.row.dispensePerDuration" style="width: 80px" :min="1"
|
<el-input-number v-model="scope.row.dispensePerDuration" style="width: 80px" :min="1"
|
||||||
controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)"
|
controls-position="right" :controls="false" :ref="(el) => (inputRefs.dispensePerDuration = el)"
|
||||||
|
@input="calculateTotalAmount(scope.row, scope.$index)"
|
||||||
@change="calculateTotalAmount(scope.row, scope.$index)"
|
@change="calculateTotalAmount(scope.row, scope.$index)"
|
||||||
@keyup.enter.prevent="
|
@keyup.enter.prevent="
|
||||||
handleEnter('dispensePerDuration', scope.row, scope.$index)
|
handleEnter('dispensePerDuration', scope.row, scope.$index)
|
||||||
|
|||||||
Reference in New Issue
Block a user