feat(borrow): 转交改为整单覆盖 + 双向握手,并修复接收人可见性
一、整单覆盖(修复漏行 / 单内撕裂)
----
transfer_borrow 现在按 borrow_no 定位**整张单**,覆盖全部未还明细;accept 时
整批转移持有权。原先只改传入的那一行,2 明细的单转完会出现两个持有人。
新增 _load_slip_for_update():按单号锁整单,且按 id 升序取行锁 —— 并发下所有
事务以相同顺序加锁,避免与归还/转交交叉加锁死锁。
二、双向握手
----
· transfer_borrow(发起):只落一条 PENDING 流水,**不再改主表 current_holder**。
东西还没到对方手上,责任仍归原持有人 —— 这是与旧实现最本质的区别。
同一单号已有 PENDING 时拒绝再次发起,避免两个接收人争抢同一批实物。
· accept_transfer:流水置 ACCEPTED,把该单**全部未还明细**的持有人改为接收人。
. reject_transfer:流水置 REJECTED,主表不动。
两者都强校验「当前登录人 == to_user_id 本人」。
★ accept/reject 刻意**不加 permission_required**:这不是库管职权,而是员工对
自己名下资产的确认动作,加库管权限会把接收人挡在门外。
三、接收人可见性(OR 过滤)
----
get_records 普通用户过滤原先只比对 borrower_name,接收人在自己的列表里看不到
已经接收的东西。现改为三种关系任一成立:
① 我是借用人
② 我是**当前持有人**(转交接收后)
③ 有一条**待我接收**的 PENDING 转交 —— 东西还在对方手上、主表尚未转移,
② 匹配不到,必须单独并入,否则接收人看不到待办、无从确认
ID 与姓名双口径并存,兼容只有姓名没有 ID 的历史行。
列表项附加 pending_transfer(含后端判定的 is_mine)—— 前端 localStorage 里
只有 username 没有 user_id,靠姓名比对既有歧义又不可靠,故由后端标记。
四、验证(合成 2 明细单,25 项断言全通过)
----
· 发起后两条明细持有人均未变(责任未转移)
· 非接收人无法 accept / reject;重复发起被拒
· ★ accept 后**两条明细**持有人一并转移(漏行修复的核心)
· 接收前凭 PENDING 分支可见、接收后凭 current_holder 可见
· reject 后主表持有人不变
· 全程 available_quantity 无变化,库存精确还原、零残留数据
This commit is contained in:
@ -1,7 +1,10 @@
|
||||
import uuid # .material -> .base refactor checked
|
||||
from datetime import datetime
|
||||
from app.extensions import db, beijing_time
|
||||
from app.models.transaction import TransBorrow, TransBorrowTransfer, TransBorrowReturn
|
||||
from app.models.transaction import (
|
||||
TransBorrow, TransBorrowTransfer, TransBorrowReturn,
|
||||
TRANSFER_STATUS_PENDING, TRANSFER_STATUS_ACCEPTED, TRANSFER_STATUS_REJECTED,
|
||||
)
|
||||
from app.models.inbound.buy import StockBuy
|
||||
from app.models.inbound.semi import StockSemi
|
||||
from app.models.inbound.product import StockProduct
|
||||
@ -509,156 +512,213 @@ class TransService:
|
||||
raise e
|
||||
|
||||
# ==========================================================================
|
||||
# 借库转交(一期)
|
||||
# 借库转交(一期 + 双向握手)
|
||||
#
|
||||
# 状态机:
|
||||
# PENDING ──accept──> ACCEPTED (主表 current_holder 正式转移)
|
||||
# └───reject──> REJECTED (主表不动,责任仍在原持有人)
|
||||
#
|
||||
# ★ 覆盖范围是**整张单**(borrow_no),不是单行明细 —— 原实现收明细行 ID
|
||||
# 只改一行,一张 2 明细的单转交后一半归新接收人、一半仍是原借用人,
|
||||
# 前端按单号聚合便同时显示两个名字(实测 BOR-20260917-0001)。
|
||||
# ==========================================================================
|
||||
@staticmethod
|
||||
def transfer_borrow(borrow_id, to_user_id, transfer_qty, operator_name='System', remark=None):
|
||||
def _load_slip_for_update(borrow_no):
|
||||
"""
|
||||
把一张借出单的**持有权**从当前持有人整单转给另一人。
|
||||
按单号锁定整单并返回全部明细行(含已归还的)。
|
||||
|
||||
与 execute_dispatch / process_return 的根本区别
|
||||
------------------------------------------------
|
||||
转交是**纯持有权变更**:实物不出入库,库存账目分毫不动。
|
||||
本方法全程不触碰 stock_buy / stock_semi / stock_product 的任何字段
|
||||
(available_quantity 与 stock_quantity 都不动)。
|
||||
理由见 restore_then_deduct 的 deduct_stock 说明:借出期间 available
|
||||
已冻结、stock 仍含借出未还量。转交若去动库存,会同时破坏两个既有假设 ——
|
||||
「归还只加 available」会算多,而「借库转报废在确认损失时扣 stock」
|
||||
(scrap_sources.BorrowScrapAdapter)会重复扣减。
|
||||
★ 按 id 升序加锁:并发下所有事务以相同顺序取行锁,避免与归还/转交
|
||||
交叉加锁造成死锁(与 execute_dispatch 的 items.sort 同一考虑)。
|
||||
"""
|
||||
return (TransBorrow.query
|
||||
.filter(TransBorrow.borrow_no == borrow_no)
|
||||
.order_by(TransBorrow.id.asc())
|
||||
.with_for_update()
|
||||
.all())
|
||||
|
||||
为什么一期只允许整单全量转交
|
||||
----------------------------
|
||||
trans_borrow 是**单行**模型,只能存一个 current_holder_id。
|
||||
若允许部分转交(借 10 个转 5 个出去),这一行的 current_holder 就必须
|
||||
同时表示两个人,语义直接撕裂,且归还时无法判定该由谁还。
|
||||
故 transfer_qty 必须严格等于待还量(quantity - returned_quantity)。
|
||||
@staticmethod
|
||||
def transfer_borrow(borrow_id, to_user_id, transfer_qty=None, operator_name='System', remark=None):
|
||||
"""
|
||||
发起转交(双向握手第一步):只落一条 PENDING 流水,**不动物权**。
|
||||
|
||||
★ 未来若需部分转交:应改为按数量**拆行**(新建一条 trans_borrow 承接
|
||||
转出量、原行扣减),而不是在本行上加字段打补丁 —— 单行模型无论如何
|
||||
扩展都无法同时表达两个持有人。
|
||||
东西还没到接收人手上,责任仍由原持有人承担 —— 这是与旧实现最本质的
|
||||
区别:旧实现「发起即生效」,接收人在毫不知情的情况下被强塞了资产责任。
|
||||
|
||||
参数
|
||||
----
|
||||
borrow_id : trans_borrow.id
|
||||
to_user_id : 接收人(转交后的 current_holder)ID
|
||||
transfer_qty : 转交数量,一期必须等于待还量
|
||||
operator_name: 执行转交操作的库管姓名
|
||||
borrow_id : 该单**任一明细行**的 ID,仅用于解析单据身份(borrow_no)
|
||||
to_user_id : 接收人ID
|
||||
transfer_qty : 可选。传入时须等于整单待还量,仅作一致性校验
|
||||
operator_name: 发起操作的库管
|
||||
remark : 转交备注
|
||||
|
||||
返回
|
||||
----
|
||||
TransBorrowTransfer 实例(已 commit)
|
||||
|
||||
异常
|
||||
----
|
||||
ValueError: 任何校验不通过(调用方整单回滚,不会留下半转状态)
|
||||
返回已 commit 的 TransBorrowTransfer
|
||||
异常 ValueError
|
||||
"""
|
||||
from app.models.system import SysUser
|
||||
|
||||
if to_user_id is None:
|
||||
raise ValueError("缺少接收人 to_user_id")
|
||||
if transfer_qty is None:
|
||||
raise ValueError("缺少转交数量 transfer_qty")
|
||||
try:
|
||||
transfer_qty = float(transfer_qty)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("转交数量格式无效,应为数字")
|
||||
|
||||
# ==================================================================
|
||||
# ★ 防线1:锁行 —— 并发下防止同一张单被同时转给两个人
|
||||
# (后到的事务会阻塞在此,拿到锁后读到已推进的 current_holder_id,
|
||||
# 从而在下方「接收人 == 当前持有人」校验处被拒绝)
|
||||
# ==================================================================
|
||||
record = TransBorrow.query.with_for_update().get(borrow_id)
|
||||
if not record:
|
||||
raise ValueError("借出记录不存在")
|
||||
|
||||
# --- 1. 状态准入 ---
|
||||
total_qty = float(record.quantity or 0)
|
||||
returned_qty = float(record.returned_quantity or 0)
|
||||
pending_qty = total_qty - returned_qty
|
||||
|
||||
# ★ 顺序有意义:报废流程(scrap_sources)会同时置 is_returned=True 与
|
||||
# status='scrapped',若先判 is_returned 会让报废单收到「已归还」的
|
||||
# 误导性提示。故先判报废,给出准确原因。
|
||||
if record.status == 'scrapped':
|
||||
raise ValueError("该借出记录已转入报废流程,不可再转交")
|
||||
if record.is_returned or pending_qty <= 0:
|
||||
raise ValueError("该借出记录已全部归还,无可转交的实物")
|
||||
|
||||
# --- 2. 数量校验:一期必须整单全量转交 ---
|
||||
if transfer_qty <= 0:
|
||||
raise ValueError("转交数量必须大于0")
|
||||
if transfer_qty > pending_qty:
|
||||
raise ValueError(
|
||||
f"转交数量({transfer_qty})不能大于待还数量({pending_qty})"
|
||||
)
|
||||
# 浮点容差:quantity/returned_quantity 是 numeric(19,4),差值应精确,
|
||||
# 但仍用容差比较,避免二进制浮点表示误差造成误拒。
|
||||
if abs(transfer_qty - pending_qty) > 1e-6:
|
||||
raise ValueError(
|
||||
f"目前仅支持整单全部转交:本单待还 {pending_qty},"
|
||||
f"本次仅转交 {transfer_qty}。部分转交会导致当前持有人语义撕裂,"
|
||||
f"请整单转交,或先办理部分归还后再转交。"
|
||||
)
|
||||
|
||||
# --- 3. 转出方必须已锚定 ---
|
||||
# 历史行(迁移前无法用姓名唯一映射到 sys_user 的)holder 为 NULL,
|
||||
# 此时「从谁转出」无从确定,Fail-Closed 拒绝。
|
||||
if record.current_holder_id is None:
|
||||
raise ValueError(
|
||||
"该借出记录的当前持有人未锚定(历史数据),无法转交,请先办理归还"
|
||||
)
|
||||
|
||||
# --- 4. 接收人校验 ---
|
||||
try:
|
||||
to_user_id = int(to_user_id)
|
||||
except (TypeError, ValueError):
|
||||
# 不直接 int() 抛裸异常:原生报错信息(invalid literal for int()...)
|
||||
# 会原样透给前端,对库管毫无指导意义。
|
||||
raise ValueError("接收人 to_user_id 格式无效,应为数字ID")
|
||||
|
||||
anchor = TransBorrow.query.get(borrow_id)
|
||||
if not anchor:
|
||||
raise ValueError("借出记录不存在")
|
||||
borrow_no = anchor.borrow_no
|
||||
if not borrow_no:
|
||||
raise ValueError("该借出记录缺少单号,无法转交")
|
||||
|
||||
rows = TransService._load_slip_for_update(borrow_no)
|
||||
open_rows = [r for r in rows if not r.is_returned]
|
||||
|
||||
# --- 1. 状态准入 ---
|
||||
if not open_rows:
|
||||
raise ValueError("该借用单已全部归还,无可转交的实物")
|
||||
if any(r.status == 'scrapped' for r in open_rows):
|
||||
raise ValueError("该借用单已转入报废流程,不可转交")
|
||||
|
||||
# --- 2. 数量:整单全量,不接受部分转交 ---
|
||||
total_pending = sum(
|
||||
float(r.quantity or 0) - float(r.returned_quantity or 0) for r in open_rows
|
||||
)
|
||||
if total_pending <= 0:
|
||||
raise ValueError("该借用单待还数量为 0,无可转交的实物")
|
||||
if transfer_qty is not None:
|
||||
try:
|
||||
transfer_qty = float(transfer_qty)
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("转交数量格式无效,应为数字")
|
||||
if abs(transfer_qty - total_pending) > 1e-6:
|
||||
raise ValueError(
|
||||
f"目前仅支持整单全部转交:本单待还 {total_pending},"
|
||||
f"本次仅转交 {transfer_qty}。部分转交会让同一张单出现两个持有人,"
|
||||
f"请整单转交,或先办理部分归还后再转交。"
|
||||
)
|
||||
|
||||
# --- 3. 转出方 = 所选明细当前的持有人 ---
|
||||
# 正常单据内各明细持有人一致;历史遗留的「单内撕裂」以所选明细为准,
|
||||
# accept 时会把该单未还明细**整体归一**到接收人名下(见 accept_transfer)。
|
||||
if anchor.current_holder_id is None:
|
||||
raise ValueError("该借出记录的当前持有人未锚定(历史数据),无法转交,请先办理归还")
|
||||
from_id = int(anchor.current_holder_id)
|
||||
from_name = anchor.current_holder_name or user_display_name(SysUser.query.get(from_id))
|
||||
|
||||
# --- 4. 接收人校验 ---
|
||||
to_user = SysUser.query.get(to_user_id)
|
||||
if not to_user:
|
||||
raise ValueError(f"接收人不存在(ID:{to_user_id})")
|
||||
to_user_name = user_display_name(to_user)
|
||||
if to_user_id == int(record.current_holder_id):
|
||||
if to_user_id == from_id:
|
||||
raise ValueError(f"接收人与当前持有人同为【{to_user_name}】,无需转交")
|
||||
|
||||
# --- 5. 行级公司隔离(Fail-Closed)---
|
||||
_assert_borrow_company_visible(record)
|
||||
# --- 5. 同一单号只允许一条待接收流水(否则两个接收人争抢同一批实物)---
|
||||
pending = TransBorrowTransfer.query.filter(
|
||||
TransBorrowTransfer.borrow_no == borrow_no,
|
||||
TransBorrowTransfer.status == TRANSFER_STATUS_PENDING,
|
||||
).first()
|
||||
if pending:
|
||||
raise ValueError(
|
||||
f"该借用单已有一条待接收的转交(接收人:"
|
||||
f"{pending.to_user_name or pending.to_user_id}),请等待对方处理"
|
||||
)
|
||||
|
||||
# --- 6. 行级公司隔离(Fail-Closed)---
|
||||
_assert_borrow_company_visible(anchor)
|
||||
|
||||
# ==================================================================
|
||||
# ★ 防线2:以下只写台账,绝不触碰任何库存字段
|
||||
# ★ 只写台账,主表 current_holder **保持不变** —— 双向握手的关键。
|
||||
# 库存字段更是一律不碰(转交是纯持有权变更,实物不出入库)。
|
||||
# ==================================================================
|
||||
from_name = record.current_holder_name or user_display_name(
|
||||
SysUser.query.get(record.current_holder_id)
|
||||
)
|
||||
from_id = record.current_holder_id
|
||||
|
||||
transfer = TransBorrowTransfer(
|
||||
borrow_id=record.id,
|
||||
borrow_id=anchor.id,
|
||||
borrow_no=borrow_no,
|
||||
status=TRANSFER_STATUS_PENDING,
|
||||
from_user_id=from_id,
|
||||
from_user_name=from_name,
|
||||
to_user_id=to_user_id,
|
||||
to_user_name=to_user_name,
|
||||
transfer_qty=transfer_qty,
|
||||
transfer_qty=total_pending,
|
||||
transfer_time=beijing_time(),
|
||||
operator_name=operator_name,
|
||||
remark=remark,
|
||||
)
|
||||
db.session.add(transfer)
|
||||
|
||||
# 推进当前持有人。borrower_id(初始借用人)保持不动 —— 它回答的是
|
||||
# 「这单最初谁借的」,不应被转交改写。
|
||||
record.current_holder_id = to_user_id
|
||||
record.current_holder_name = to_user_name
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
return transfer
|
||||
|
||||
@staticmethod
|
||||
def accept_transfer(transfer_id, user_id):
|
||||
"""
|
||||
接收转交(双向握手第二步):流水置 ACCEPTED,并**正式转移持有权**。
|
||||
|
||||
覆盖范围 = 该单号下**全部未还明细**。正常单据各明细持有人一致,整批转移
|
||||
天然无歧义;若遇历史遗留的「单内撕裂」,此处的整批归一同时把它修复 ——
|
||||
一张单本就只应有一个持有人。
|
||||
|
||||
权限:仅 to_user_id 本人(这是员工对自己名下资产的确认,不是库管权限)。
|
||||
返回 (transfer, 本次转移的明细行数)
|
||||
"""
|
||||
transfer = TransBorrowTransfer.query.with_for_update().get(transfer_id)
|
||||
if not transfer:
|
||||
raise ValueError("转交记录不存在")
|
||||
if transfer.status != TRANSFER_STATUS_PENDING:
|
||||
raise ValueError(f"该转交已【{transfer.to_dict()['status_text']}】,无法重复处理")
|
||||
if transfer.to_user_id is None or int(transfer.to_user_id) != int(user_id):
|
||||
raise ValueError("只有该转交的接收人本人可以确认接收")
|
||||
if not transfer.borrow_no:
|
||||
raise ValueError("该转交记录缺少单号(历史数据),无法确认接收")
|
||||
|
||||
rows = TransService._load_slip_for_update(transfer.borrow_no)
|
||||
open_rows = [r for r in rows if not r.is_returned]
|
||||
if not open_rows:
|
||||
raise ValueError("该借用单已全部归还,无需接收")
|
||||
|
||||
to_name = transfer.to_user_name
|
||||
if not to_name:
|
||||
from app.models.system import SysUser
|
||||
to_name = user_display_name(SysUser.query.get(transfer.to_user_id))
|
||||
|
||||
for r in open_rows:
|
||||
r.current_holder_id = int(transfer.to_user_id)
|
||||
r.current_holder_name = to_name
|
||||
|
||||
transfer.status = TRANSFER_STATUS_ACCEPTED
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
return transfer, len(open_rows)
|
||||
|
||||
@staticmethod
|
||||
def reject_transfer(transfer_id, user_id, reason=None):
|
||||
"""
|
||||
拒绝转交:流水置 REJECTED,**主表不动** —— 责任仍在原持有人。
|
||||
权限:仅 to_user_id 本人。
|
||||
"""
|
||||
transfer = TransBorrowTransfer.query.with_for_update().get(transfer_id)
|
||||
if not transfer:
|
||||
raise ValueError("转交记录不存在")
|
||||
if transfer.status != TRANSFER_STATUS_PENDING:
|
||||
raise ValueError(f"该转交已【{transfer.to_dict()['status_text']}】,无法重复处理")
|
||||
if transfer.to_user_id is None or int(transfer.to_user_id) != int(user_id):
|
||||
raise ValueError("只有该转交的接收人本人可以拒绝")
|
||||
|
||||
transfer.status = TRANSFER_STATUS_REJECTED
|
||||
if reason:
|
||||
transfer.remark = (f"{transfer.remark}\n" if transfer.remark else "") + f"[拒绝原因] {reason}"
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise e
|
||||
return transfer
|
||||
|
||||
@staticmethod
|
||||
@ -861,7 +921,7 @@ class TransService:
|
||||
@staticmethod
|
||||
def get_records(page=1, limit=10, status='all', keyword=None, search_type='all',
|
||||
borrower_name=None, start_date=None, end_date=None,
|
||||
advanced_filters=None):
|
||||
advanced_filters=None, viewer_user_id=None, current_user_id=None):
|
||||
"""
|
||||
获取借还记录列表(按单号 borrow_no 维度分页,避免明细撑爆 pageSize)
|
||||
|
||||
@ -1106,21 +1166,54 @@ class TransService:
|
||||
# ====================================================================
|
||||
borrow_no_q = db.session.query(order_subq.c.borrow_no)
|
||||
|
||||
# ★ 数据权限:普通用户只看“借用人=本人姓名(不含账号前缀)”的借还记录;
|
||||
# 兼容库里存成“姓名/xiaolongxia”全名(姓名 + '/' 前缀)的情况
|
||||
if borrower_name:
|
||||
# ====================================================================
|
||||
# ★ 数据权限:普通用户能看到「与自己有关」的借还记录,三种关系任一成立:
|
||||
# ① 我是借用人
|
||||
# ② 我是**当前持有人** —— 转交接收后东西在我手上,此前只比对
|
||||
# borrower_name,接收人在自己的列表里根本看不到该单
|
||||
# ③ 有一条**待我接收**的转交(PENDING)—— 东西还在对方手上、
|
||||
# 主表尚未转移,②匹配不到,必须单独并入,否则接收人看不到
|
||||
# 待办、无从确认
|
||||
# ID 与姓名双口径并存:新数据有 ID 锚点,历史行只有姓名。
|
||||
# ====================================================================
|
||||
if borrower_name or viewer_user_id:
|
||||
own_conds = []
|
||||
if borrower_name:
|
||||
# 兼容库里存成「姓名/xiaolongxia」全名的情况
|
||||
own_conds.append(TransBorrow.borrower_name == borrower_name)
|
||||
own_conds.append(TransBorrow.borrower_name.like(f"{borrower_name}/%"))
|
||||
own_conds.append(TransBorrow.current_holder_name == borrower_name)
|
||||
own_conds.append(TransBorrow.current_holder_name.like(f"{borrower_name}/%"))
|
||||
if viewer_user_id:
|
||||
own_conds.append(TransBorrow.borrower_id == viewer_user_id)
|
||||
own_conds.append(TransBorrow.current_holder_id == viewer_user_id)
|
||||
|
||||
own_borrow_nos_subq = (
|
||||
db.session.query(TransBorrow.borrow_no)
|
||||
.filter(or_(
|
||||
TransBorrow.borrower_name == borrower_name,
|
||||
TransBorrow.borrower_name.like(f"{borrower_name}/%")
|
||||
))
|
||||
.filter(or_(*own_conds))
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
borrow_no_q = borrow_no_q.filter(
|
||||
order_subq.c.borrow_no.in_(own_borrow_nos_subq)
|
||||
)
|
||||
|
||||
if viewer_user_id:
|
||||
pending_to_me_subq = (
|
||||
db.session.query(TransBorrowTransfer.borrow_no)
|
||||
.filter(
|
||||
TransBorrowTransfer.to_user_id == viewer_user_id,
|
||||
TransBorrowTransfer.status == TRANSFER_STATUS_PENDING,
|
||||
TransBorrowTransfer.borrow_no.isnot(None),
|
||||
)
|
||||
.distinct()
|
||||
.subquery()
|
||||
)
|
||||
borrow_no_q = borrow_no_q.filter(or_(
|
||||
order_subq.c.borrow_no.in_(own_borrow_nos_subq),
|
||||
order_subq.c.borrow_no.in_(pending_to_me_subq),
|
||||
))
|
||||
else:
|
||||
borrow_no_q = borrow_no_q.filter(
|
||||
order_subq.c.borrow_no.in_(own_borrow_nos_subq)
|
||||
)
|
||||
|
||||
# 关键词过滤
|
||||
if keyword_borrow_nos_subq is not None:
|
||||
@ -1331,6 +1424,33 @@ class TransService:
|
||||
key=lambda d: (_order_idx.get(d.get('borrow_no'), len(_order_idx)), d.get('id') or 0)
|
||||
)
|
||||
|
||||
# ====================================================================
|
||||
# ★ 附加「待接收的转交」:前端据此渲染【接收转交】【拒绝】按钮与
|
||||
# 「转交待确认」状态。批量查一次,避免逐单 N+1。
|
||||
# 应用层保证同一单号最多一条 PENDING,故 borrow_no 可直接作键。
|
||||
# ====================================================================
|
||||
if items_with_names:
|
||||
_bnos = [d.get('borrow_no') for d in items_with_names if d.get('borrow_no')]
|
||||
_pending = TransBorrowTransfer.query.filter(
|
||||
TransBorrowTransfer.borrow_no.in_(_bnos),
|
||||
TransBorrowTransfer.status == TRANSFER_STATUS_PENDING,
|
||||
).all() if _bnos else []
|
||||
_pending_map = {t.borrow_no: t.to_dict() for t in _pending}
|
||||
for d in items_with_names:
|
||||
_pt = _pending_map.get(d.get('borrow_no'))
|
||||
if _pt is not None:
|
||||
# ★ is_mine 由后端判定:前端 localStorage 里只有 username
|
||||
# 没有 user_id,靠姓名比对既有歧义又不可靠。
|
||||
# (viewer_user_id 对管理者为 None,故另取 current_user_id)
|
||||
_pt['is_mine'] = (
|
||||
current_user_id is not None
|
||||
and _pt.get('to_user_id') is not None
|
||||
and int(_pt['to_user_id']) == int(current_user_id)
|
||||
)
|
||||
d['pending_transfer'] = _pt
|
||||
else:
|
||||
_pending_map = {}
|
||||
|
||||
return {
|
||||
'items': items_with_names,
|
||||
'total': total_orders,
|
||||
|
||||
Reference in New Issue
Block a user