Files
his/md/bug-analysis/bug445-analysis.md

5.7 KiB
Raw Blame History

Bug #445 分析报告

Bug 描述

在"门诊手术临时医嘱"界面,生成医嘱成功后,已生成的计费项目仍然保留在"一、已引用计费药品(待生成医嘱)"列表中,导致上下两个列表数据完全一致,用户无法区分哪些已处理、哪些未处理。

根因定位

核心问题:handleTemporaryMedicalSubmit 中过滤逻辑匹配字段路径错误

文件: openhis-ui-vue3/src/views/surgicalschedule/index.vue 行号: 第 1791-1793 行

// 第 1776-1788 行:构建已提交项目的匹配键(从 originalMedicine 中取字段)
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')
)

// 第 1791-1794 行:过滤待生成列表(错误:直接从顶层取字段)
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
  const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`  // ❌ BUG: 字段路径错误
  return !submittedKeys.has(key)
})

为什么匹配不上?

temporaryBillingMedicines 中的数据来自 handleMedicalAdvice(第 1605-1651 行),转换后的对象结构为:

{
  medicineName: 'xxx',      // 顶层有(从原始 item 映射来的)
  specification: 'xxx',     // 顶层有
  quantity: xxx,            // 顶层有
  originalMedicine: {       // 嵌套也有
    medicineName: 'xxx',    // ...spread 复制了所有字段
    specification: 'xxx',
    quantity: xxx,
    encounterId: xxx
  }
}

但问题在于 handleQuoteBilling(第 1878-1916 行)刷新数据时的结构不同:

// handleQuoteBilling 中的数据映射(第 1878-1897 行)
{
  medicineName: 'xxx',      // 顶层有
  specification: 'xxx',     // 顶层有
  quantity: xxx,            // 顶层有
  // ❌ 没有 originalMedicine 嵌套!
}

等等,让我再仔细看...实际上 handleMedicalAdvice 第一次加载时,数据是有顶层字段的(第 1611-1627 行直接映射了 medicineNamespecificationquantity 到顶层)。所以匹配键应该能对上。

让我重新审视...

重新分析:真正的问题

再看 handleMedicalAdvice 第 1560-1562 行:

// 先清空旧数据
temporaryBillingMedicines.value = []
temporaryAdvices.value = []

这是问题的关键! 当用户第二次打开同一个手术记录的医嘱界面时:

  1. isSameEncounter 检查(第 1543-1556 行):

    • temporaryAdvices.value.length > 0 → 此时已被清空为 0所以 isSameEncounter = false
    • 不会走 early return
  2. 第 1560-1562 行清空了 temporaryAdvices

  3. 调用 getPrescriptionList 从后端拉取最新数据

  4. 第 1587-1588 行过滤:if (item.requestId) return false;

    • 后端新创建的医嘱记录,返回的数据中 requestId 可能为空/null
    • 因为这些记录刚被创建后端的计费数据表adm_charge_item可能还没同步 requestId
  5. 结果:已生成的医嘱项目因为 requestId 为空,没有被过滤掉,重新出现在"待生成"列表中

另一个问题:提交后的本地过滤也不可靠

handleTemporaryMedicalSubmit(第 1791 行)的本地过滤:

const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`

这里的 mtemporaryBillingMedicines 中的对象。在 handleMedicalAdvice 首次加载时(第 1605-1651 行),确实映射了顶层字段,所以这个匹配应该能工作

但问题在于:提交成功后,如果用户点击了"刷新"按钮或"引用计费"按钮:

  • handleQuoteBilling 从后端重新拉取数据
  • 后端返回的数据中,新生成的医嘱项 requestId 仍然可能为空
  • 虽然 handleQuoteBilling 有第 1977-1999 行的过滤逻辑,但它依赖于 temporaryAdvices 中已有的 requestId/chargeItemId/id
  • 如果这些 ID 在后端返回的新数据中不存在,就匹配不上

修复方案

方案:在 handleMedicalAdvice 中,提交后再次打开时保留 temporaryAdvices 数据

核心修复点:不要在打开医嘱时清空 temporaryAdvices,而是复用已提交的数据,避免从后端重复拉取已生成的项目。

具体修改 handleMedicalAdvice 函数:

  1. 将清空数据的逻辑移到 isSameEncounter 检查之后,并且只在非同一 encounter 时才清空
  2. 或者,在从后端拉取数据后,用已提交的 temporaryAdvices 中的 requestId 来过滤后端返回的数据

最简洁的修复:在 handleMedicalAdvice 第 1559-1562 行,不要无条件清空 temporaryAdvices。改为:

// 修复前:
temporaryBillingMedicines.value = []
temporaryAdvices.value = []

// 修复后:
temporaryBillingMedicines.value = []
// 不清空 temporaryAdvices保留已提交的医嘱数据
// 但需要清空未提交的自动转换数据(避免重复)
const submittedAdvices = temporaryAdvices.value.filter(a => a.originalMedicine?.requestId)
temporaryAdvices.value = submittedAdvices

同时,在 getPrescriptionList 回调中(第 1571 行之后),用已提交的 requestId 过滤后端返回的数据。

总结

  • 根因handleMedicalAdvice 每次打开都清空 temporaryAdvices,然后从后端重新拉取数据。但后端返回的新创建医嘱项可能没有 requestId,导致无法过滤。
  • 修复:保留已提交(有 requestId的医嘱数据不清空同时用这些 requestId 过滤后端返回的新数据。