fix(borrow): 恢复选中审批单后的「一键带出」,并补上领用人自动回填

现象
----
借库执行页选中一张已通过的审批单后,下方的「预计归还日期」「备注说明」
不再自动带出;「领用人」在改为用户下拉后也恒为空,库管每次都得手工补填。

根因(不在前端下拉框,而在后端持久化)
----
borrow_service.submit_approval 会把 reserve_for_items() 的返回值直接
set_items() 落库 —— 即审批单的 items_json **就是预占结果**。

而 reserve_for_items 内部是 `reserved.append({...})` **重建**字典,只保留
库存定位与数量,把申请端按明细传上来的 expected_return_time / is_indefinite
全部丢弃。执行页读的正是 firstItem.expected_return_time,读不到就落到
「清空」分支。

这是 2026-09-10 库存预占改造引入的回退:

    b57c21a  fix 前:  approval.set_items(items)           # 原始申请明细
    b57c21a  fix 后:  approval.set_items(reserved_items)  # 重建字典,字段丢失

数据完全吻合该提交的上线时刻(09-10 14:59):
    最后一张「有日期」的审批单  id=40  创建于 09-10 09:30
    第一张「丢失日期」的审批单 id=41  创建于 09-10 16:20

修复
----
1) inventory_reservation.reserve_for_items:透传申请「意图字段」
   (expected_return_time / is_indefinite / remark)到预占结果。
   · 一条申请明细会被拆到多个批次行(同物料多批次),故按 base_id 建索引,
     把同一份意图回填到它拆分出的每一行;
   · **只透传意图,不透传库存定位与数量** —— 实扫可能换批次,
     这里写什么执行页就按什么释放,绝不能覆盖分配结果;
   · 跳过 None:无限期借用提交的 expected_return_time 本就是 null。

2) borrow.vue handleApprovalChange:实现领用人自动回填
   · 口径差异:审批单上的 borrower_name 是**完整 username**
     (「杜邢宸/duxingchen」),人员名单返回的是展示名(「杜邢宸」)。
     两边归一化到「斜杠前段」再比对,否则永远匹配不上;
   · borrower_name 优先,匹配不到才回退 applicant_id —— 库管代建时申请人是
     库管本人,回退到它会选错人;
   · 名单加载改为可重复 await:选中单据时要拿它反查,名单没回来就比对会
     误判为「找不到」而清空;
   · 匹配不到时保持未选,不回退到自由文本 —— 借用人 ID 是转交/归还责任链的
     唯一锚点,宁可让库管手选也不能猜。

可编辑性
----
三个字段均保持可改:领用人下拉、备注文本框无 disabled;日期选择器仅在勾选
「无限期/长期借用」时置灰(原有语义,取消勾选即可重新填写)。

影响与验证
----
· 存量:仅 1 张待执行审批单(id=46)受影响,其日期在库中已无任何留存,
  无法回填,需库管手工补一次;已完结单据不受影响。
· 回归:reserve_for_items 透传 9 项断言、submit_approval 端到端 5 项断言
  全部通过;库存精确还原、无残留数据。
This commit is contained in:
yueli
2026-09-17 09:26:42 +08:00
parent ece5dd8d72
commit 8d83d4404e
2 changed files with 87 additions and 2 deletions

View File

@ -532,6 +532,37 @@ const handleApprovalChange = async (val: number | null) => {
}
// ★ 自动关联:把该借库申请单的“申请原因(remark)”自动带入执行备注,库管无需重复填写(可手动改)
form.remark = (req && req.remark) || ''
// ★ 领用人自动回填
//
// 口径差异:审批单上的 borrower_name 存的是**完整 username**
// (形如「杜邢宸/duxingchen」而人员名单返回的是展示名「杜邢宸」
// 后端已按斜杠前段截断)。两边都归一化到「斜杠前段」再比对,
// 否则永远匹配不上、下拉框恒为空。
//
// 优先级borrower_name 优先 —— 它才是「谁要借」的意图;库管代建时
// applicant_id 是库管本人,回退到它会选错人。仅当审批单没记借用人姓名时
// 才回退到申请人。
//
// 匹配不到时**保持未选**,不回退到自由文本:借用人 ID 是转交/归还责任链的
// 唯一锚点,宁可让库管手动选,也不能猜一个。
await loadBorrowerOptions()
form.borrower_id = null
form.borrower_name = ''
if (req) {
const wantName = String(req.borrower_name || '').split('/')[0].trim()
let hit = wantName
? borrowerOptions.value.find(u => String(u.name || '').split('/')[0].trim() === wantName)
: undefined
if (!hit && req.applicant_id) {
hit = borrowerOptions.value.find(u => u.id === req.applicant_id)
}
if (hit) {
form.borrower_id = hit.id
form.borrower_name = hit.name
}
}
cartItems.value = []
signatureFile.value = null
signaturePreviewUrl.value = ''
@ -734,11 +765,17 @@ const form = reactive({
// ★ 借用人名单:与转交接收人、归还人共用同一数据源,按公司隔离
const borrowerOptions = ref<Array<{ id: number; name: string }>>([])
let borrowerOptionsLoaded = false
// 可重复调用:已加载则直接返回;必须可 await ——
// ★ 选中审批单时要拿这份名单反查借用人,若名单尚未返回就比对,
// 会误判为「找不到」而清空选择。
const loadBorrowerOptions = async () => {
if (borrowerOptionsLoaded) return
try {
const res: any = await getBorrowUsers()
borrowerOptions.value = res?.data || []
borrowerOptionsLoaded = true
} catch {
borrowerOptions.value = []
}