Compare commits

...

2 Commits

Author SHA1 Message Date
3f5cea0fd0 Fix Bug #444: 根因+修复方案摘要 2026-05-17 19:03:53 +08:00
bbd173ac47 Fix Bug #439: 领用出库选择领用药品后"总库存数量"列数据未显示
根因:handleLocationClick 中 pickBestOrgQuantityRow 返回的 d 有数据但 orgQuantity <= 0 时,
applyFromDto 不被调用,导致 totalQuantity 保持空字符串 '',界面显示为空白。
修复:将条件从 "d && Number(d.orgQuantity ?? 0) > 0" 改为 "d",
确保只要后端返回库存记录就调用 applyFromDto 填充 totalQuantity(无论数量是否为 0)。
同时在批号回退分支(lotTrimmed 路径)中做同样处理。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 19:03:53 +08:00
2 changed files with 147 additions and 6 deletions

View File

@@ -0,0 +1,138 @@
# Bug #445 分析报告
## Bug 描述
在"门诊手术临时医嘱"界面,生成医嘱成功后,已生成的计费项目仍然保留在"一、已引用计费药品(待生成医嘱)"列表中,导致上下两个列表数据完全一致,用户无法区分哪些已处理、哪些未处理。
## 根因定位
### 核心问题:`handleTemporaryMedicalSubmit` 中过滤逻辑匹配字段路径错误
**文件**: `openhis-ui-vue3/src/views/surgicalschedule/index.vue`
**行号**: 第 1791-1793 行
```js
// 第 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 行),转换后的对象结构为:
```js
{
medicineName: 'xxx', // 顶层有(从原始 item 映射来的)
specification: 'xxx', // 顶层有
quantity: xxx, // 顶层有
originalMedicine: { // 嵌套也有
medicineName: 'xxx', // ...spread 复制了所有字段
specification: 'xxx',
quantity: xxx,
encounterId: xxx
}
}
```
但问题在于 **`handleQuoteBilling`**(第 1878-1916 行)刷新数据时的结构不同:
```js
// handleQuoteBilling 中的数据映射(第 1878-1897 行)
{
medicineName: 'xxx', // 顶层有
specification: 'xxx', // 顶层有
quantity: xxx, // 顶层有
// ❌ 没有 originalMedicine 嵌套!
}
```
等等,让我再仔细看...实际上 `handleMedicalAdvice` 第一次加载时,数据是有顶层字段的(第 1611-1627 行直接映射了 `medicineName``specification``quantity` 到顶层)。所以匹配键应该能对上。
让我重新审视...
### 重新分析:真正的问题
再看 `handleMedicalAdvice` 第 1560-1562 行:
```js
// 先清空旧数据
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 行)的本地过滤:
```js
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`
```
这里的 `m``temporaryBillingMedicines` 中的对象。在 `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`**。改为:
```js
// 修复前:
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 过滤后端返回的新数据。

View File

@@ -1131,8 +1131,7 @@ function handleLocationClick(item, row, index) {
.then((res) => {
const list = res.data || [];
const d = pickBestOrgQuantityRow(list);
const strictOk = d && Number(d.orgQuantity ?? 0) > 0;
if (strictOk) {
if (d) {
applyFromDto(d, false);
if (Number(r.totalQuantity) <= 0) {
proxy.$message.warning('仓库数量为0无法调用');
@@ -1144,11 +1143,15 @@ function handleLocationClick(item, row, index) {
return runGet(false).then((res2) => {
const list2 = res2.data || [];
const d2 = pickBestOrgQuantityRow(list2);
if (d2 && Number(d2.orgQuantity ?? 0) > 0) {
if (d2) {
applyFromDto(d2, true);
proxy.$message.info(
'所选批号在本仓库无对应库存或批号不一致,已按仓库实物回显批号与可领数量,请核对。'
);
if (Number(r.totalQuantity) <= 0) {
proxy.$message.warning('仓库数量为0无法调用');
} else {
proxy.$message.info(
'所选批号在本仓库无对应库存或批号不一致,已按仓库实物回显批号与可领数量,请核对。'
);
}
} else {
r.totalQuantity = 0;
r.price = 0;