yueli cccd6f6081 feat(borrow): 转交接口、身份ID锚点与流转时间线后端
责任链收口
----
· execute_dispatch:强制 borrower_id,姓名一律由 sys_user 反查,**不回退**到
  申请单姓名 —— 申请单上的姓名是「申请意向」,与扫码时实际来领的人本就可能
  不同(库管代建场景尤甚),回退会让 current_holder 从第一刻就记错人。
  同时落库 dispatch_operator,补齐「谁经手发货」。
· process_return:新增 returner_id,记录有 current_holder_id 时强校验二者相等,
  不符即整单回滚(原实现只要持有 op_return:operation 即可归还任何人借出的
  物品,函数内从不读取 borrower_name,归还环节责任链是断的);
  每次归还写 trans_borrow_return 流水;全量归还清空 current_holder
  (borrower_id 保留作历史)。
· transfer_borrow(新):整单全量转交,写转交流水 + 推进主表 current_holder。

为什么一期只允许整单全量转交
----
trans_borrow 是单行模型,只能存一个 current_holder_id。部分转交(借 10 转 5)
会让这一行同时表示两个人,语义直接撕裂,且归还时无法判定该由谁还。
若未来需支持,必须改为按数量**拆行**(新建一条承接转出量、原行扣减),
而不是在单行上加字段打补丁。

为什么转交绝不触碰库存
----
借出期间 available 已冻结、stock 仍含借出未还量(deduct_stock=False)。
转交若动库存,会同时破坏两个既有假设:「归还只加 available」会算多,
「借库转报废在确认损失时扣 stock」(scrap_sources.BorrowScrapAdapter)
会重复扣减。

接口
----
· POST /borrow/<id>/transfer      转交(borrow_transfer 权限 + 防抖锁 + 行锁)
· GET  /borrow/<id>/history       单品流转历史(供精确追溯)
· GET  /borrow/slip/<no>/history  整单时间线(实测单号最多 21 条明细,
                                  逐条调用会产生 21 个请求,故聚合返回)
· GET  /borrow/users              人员名单(借出/转交/归还共用,公司隔离)

一个隐蔽缺陷(本轮发现并修复)
----
整单时间线初版按展示用的时间字符串排序,而该字符串截断到**秒** —— 同一秒内的
连续动作(如「转交 A→B 紧接着转交 B→C」)会退化为并列,顺序取决于数据库返回
顺序,时间线随机错乱。已改为按真实 datetime 排序,并加回归用例锁定。
2026-09-17 09:17:57 +08:00
2026-09-16 17:51:32 +08:00
2026-04-28 16:07:11 +08:00
2026-04-28 16:07:11 +08:00
2026-04-28 16:07:11 +08:00
Description
No description provided
88 MiB
Languages
Python 70.2%
CSS 14.9%
Vue 7.6%
HTML 4.2%
TypeScript 3.1%