diff --git a/docs/bug439_analysis.md b/docs/bug439_analysis.md new file mode 100644 index 000000000..e2664227b --- /dev/null +++ b/docs/bug439_analysis.md @@ -0,0 +1,119 @@ +# Bug #439 分析报告 + +## Bug描述 +领用出库:选择领用药品后"总库存数量"列数据未显示 + +## 数据流分析 + +1. 用户点击"添加行" → 新增一行,totalQuantity 初始化为空字符串 '' +2. 用户在"项目"列通过 PopoverList 选择药品 → 触发 `selectRow(rowValue, index)` +3. `selectRow` 设置药品基本信息,然后调用 `handleLocationClick(1, rowValue, index)` +4. `handleLocationClick` 调用 `getCount({ itemId, orgLocationId })` 获取库存 +5. `getCount` 返回 LocationInventoryDto[] 列表,前端通过 `pickBestOrgQuantityRow` 选最大值 +6. `applyFromDto` 设置 `r.totalQuantity = d.orgQuantity || 0` + +## 根因定位 + +在 `selectRow` 函数中(第1022-1049行),选择药品后: +```javascript +form.purchaseinventoryList[index].unitList = rowValue.unitList[0]; +``` + +但后端 `/app-common/inventory-item` 接口返回的 `unitList` 只设置了 `unitCode` 和 `minUnitCode`,**没有设置 `unitCode_dictText` 和 `minUnitCode_dictText`**。 + +在 `handleLocationClick` → `applyFromDto` 中(第1099-1121行): +```javascript +r.unitCode = r.unitList.minUnitCode; +r.unitCode_dictText = r.unitList.minUnitCode_dictText; // ← undefined! +if (r.unitCode == r.unitList.minUnitCode) { // ← 这个条件始终为 true + r.price = d.price / r.partPercent || ''; + r.price = r.price.toFixed(4); +} +``` + +关键问题:`r.unitCode` 刚被设为 `r.unitList.minUnitCode`,然后条件 `r.unitCode == r.unitList.minUnitCode` 始终为 true, +导致即使价格很小(如 0.05/1=0.05),也会进入这个分支。 + +但这不是总库存数量未显示的根本原因。 + +**真正根因:`handleLocationClick` 函数在调用 `getCount` 获取库存数据后,`applyFromDto` 中 `r.totalQuantity = d.orgQuantity || 0` 的赋值逻辑依赖 `d.orgQuantity > 0` 的前置判断。** + +查看前端代码流程: +- `selectRow` 设置 `totalQuantity: ''`(新增行时的默认值) +- 然后调用 `handleLocationClick` → `getCount` → 后端返回数据 +- `pickBestOrgQuantityRow` 从返回列表中选出 orgQuantity 最大的记录 +- 如果 `d && Number(d.orgQuantity ?? 0) > 0` → 调用 `applyFromDto` → 设置 `r.totalQuantity = d.orgQuantity || 0` +- 如果条件不满足(所有记录 orgQuantity 都为 0 或返回空列表)→ **`applyFromDto` 不被调用** → `r.totalQuantity` 保持空字符串 '' + +进一步分析发现: +- 如果后端 `getCount` 返回空列表(该药品在该仓库无库存),`d` 为 null,`applyFromDto` 不会被调用 +- 但如果该药品在仓库确实有库存,问题可能出在前端数据传递上 + +**核心问题在于 `unitList` 结构不完整:** +`selectRow` 中 `rowValue.unitList` 来自药品列表查询结果,其 `unitList` 由后端 `CommonServiceImpl.getInventoryItemList` 构建, +只包含 `unitCode` 和 `minUnitCode`,缺少 `unitCode_dictText` 和 `minUnitCode_dictText`。 + +在 `handleLocationClick` 的 `applyFromDto` 中,`r.unitCode` 和 `r.unitCode_dictText` 的赋值依赖于 `unitList` 中的字段。 +如果 `r.unitList` 是从 `rowValue.unitList[0]` 赋值而来(在 `selectRow` 中),那它应该至少有 `unitCode` 和 `minUnitCode`。 + +**但是!** 编辑模式(`getTransferProductDetails`)中,`unitList` 的构建方式不同: +```javascript +form.purchaseinventoryList[index].unitList = e.unitList[0]; // 编辑详情时 +``` + +新增模式(`selectRow`)中: +```javascript +form.purchaseinventoryList[index].unitList = rowValue.unitList[0]; +``` + +两种方式获取的 `unitList` 结构可能不同。 + +**根本原因:** +`handleLocationClick` 中的 `getCount` API 调用,返回的 `LocationInventoryDto` 确实包含 `orgQuantity`。 +前端通过 `pickBestOrgQuantityRow` 选出最大值的记录后,调用 `applyFromDto` 设置 `totalQuantity`。 +如果药品在仓库有库存但 `totalQuantity` 仍为空白,说明 `applyFromDto` 中的 `d.orgQuantity` 可能为 `null`/`undefined`。 + +经检查 `selectInventoryItemInfo` SQL: +```sql +SUM(CASE WHEN T1.location_id = #{orgLocationId} THEN T1.quantity ELSE 0 END) AS org_quantity +``` + +当 `objLocationId` 为 null/空时,WHERE 子句为: +```sql +AND T1.location_id = #{orgLocationId} +``` + +这意味着查询结果中的所有记录都来自 `orgLocationId` 对应的仓库。 +此时 `org_quantity` 应该等于 `SUM(T1.quantity)`。 + +**如果查询结果为空(该药品在该仓库没有库存记录),则前端 `d` 为 null,`applyFromDto` 不被调用,totalQuantity 保持空字符串。** + +但 Bug 的期望是"应实时检索并填充总库存数量"——如果仓库确实没有该药品的库存,那显示空白是合理的。 +但如果仓库有库存却未显示,说明前端传递的参数(orgLocationId 或 itemId)有问题。 + +**最终根因:前端 `handleLocationClick` 函数中,`orgLocationId` 的取值可能为空字符串,** +**导致后端查询时使用空字符串作为 location_id 条件,查不到任何记录。** + +```javascript +let orgLocationId = r.sourceLocationId || receiptHeaderForm.headerLocationId || ''; +``` + +虽然 Bug 步骤中说先选了"西药库",但如果 `receiptHeaderForm.headerLocationId` 在 selectRow 时已正确设置, +`r.sourceLocationId` 也应该被设置(在 selectRow 第1037行): +```javascript +form.purchaseinventoryList[index].sourceLocationId = + receiptHeaderForm.headerLocationId || form.purchaseinventoryList[index].sourceLocationId || ''; +``` + +**但这里有一个微妙的时序问题:`handleLocationClick` 在 `getPharmacyCabinetList().then()` 内部被调用,** +**但 `handleLocationClick` 是同步执行的,不等待 `getPharmacyCabinetList` 完成。** +**这本身不影响 `orgLocationId` 的取值,因为 `orgLocationId` 不依赖 `getPharmacyCabinetList`。** + +## 修复方案 + +1. 确保 `applyFromDto` 即使在 `orgQuantity` 为 0 时也能被调用,正确显示"0"而不是空白 +2. 确保 `unitList` 包含必要的字典文本字段 + +## 影响范围 +- 前端文件:openhis-ui-vue3/src/views/medicationmanagement/requisitionManagement/requisitionManagement/index.vue +- 涉及函数:`selectRow`、`handleLocationClick`