feat(borrow): 借还记录「已归还」页签改为按归还时间倒序

问题
----
三个页签共用同一套排序(有限期单在前 → 最早应还时间 ASC → 最早借出时间 DESC),
这套「优先关注快到期/逾期」的逻辑对「未归还」是对的,但对「已归还」正好**
反了**:已归还列表要回答的是「最近还了哪几笔」,而按应还时间排会让最近刚还的
那几笔排到最后。

改动
----
order_subq 增加 max_return_time(单号内**最晚**一次归还时间)作为排序键,
并按页签分流:
  · 已归还     → nullslast(max_return_time DESC),borrow_no DESC 兜底保证稳定
  · 全部/未归还 → 原三级排序不变

★ 为什么取「最晚」而不是「最早」一次归还时间
  多明细分批归还时,整单结清的那一刻才是有意义的节点;且与主行「归还时间」列
  的展示口径一致(前端同样取 latest),排序依据与可见值不会打架。

验证(真实数据)
  已归还页签:09-15 11:41 → 09-14 16:02 → 09-09 11:46 → 09-08 13:22 →
              09-04 17:26 → 09-04 09:45 → 09-03 15:25,第 2 页续 08-27 → …,
              严格递减且**跨页连续**;
  未归还页签:排序与改动前完全一致(回归确认)。
This commit is contained in:
yueli
2026-09-18 09:34:45 +08:00
parent ccdd6e7306
commit e3fe1fc12a

View File

@ -1089,7 +1089,10 @@ class TransService:
# 无限期梯队内:单号内最早借出时间(呆滞借用盘点)
func.min(TransBorrow.borrow_time).label('min_borrow_time'),
# 单内是否含"有限期"明细:任一 expected_return_time 非空 → 1有限期单排前
func.max(case((TransBorrow.expected_return_time.isnot(None), 1), else_=0)).label('has_finite')
func.max(case((TransBorrow.expected_return_time.isnot(None), 1), else_=0)).label('has_finite'),
# 「已归还」页签的排序键:单号内**最晚**一次归还时间
# (多明细分批归还时,整单结清的那一刻才是有意义的节点)
func.max(TransBorrow.return_time).label('max_return_time')
)
.group_by(TransBorrow.borrow_no)
.subquery()
@ -1434,18 +1437,31 @@ class TransService:
)
)
# ★ 默认排序(多级复合,符合"优先关注快到期/逾期"业务)
# 1) 有限期单(含 expected_return_time排前无限期单排后
# 2) 有限期内按最早预计归还时间 ASC越快到期/逾期越久越靠前)
# 3) 无限期内按最早借出时间 ASC借出越久越靠前暴露长期未还的呆滞借用
borrow_no_q = borrow_no_q.order_by(
case((order_subq.c.has_finite == 0, 1), else_=0).asc(),
nullslast(asc(order_subq.c.sort_key)),
# ★ 无限期梯队内按借出时间**从近到远**desc)。
# 原实现是 asc「借出越久越靠前」设计意图是暴露呆滞借用
# 业务方明确要求改为从近到远,故反转。
desc(order_subq.c.min_borrow_time)
# ★ 排序按页签分开
#
# 「已归还」页签 —— 按**归还时间倒序**(从近到远)。
# 这张列表此时回答的是「最近还了哪几笔」,而不是「哪笔快到期」,
# 所以不能沿用未归还那套「逾期优先」的排序,否则最近刚还的
# 反而排在最后。取单号内**最晚**一次归还时间:多明细分批归还时,
# 整单结清的那一刻才是有意义的节点,也与主行「归还时间」列的
# 展示口径一致(前端同样取 latest)。
#
# 其余页签(全部 / 未归还)—— 沿用「优先关注快到期/逾期」:
# 1) 有限期单(含 expected_return_time排前无限期单排后
# 2) 有限期内按最早预计归还时间 ASC越快到期/逾期越久越靠前)
# 3) 无限期内按最早借出时间 DESC从近到远
_order_by = (
[nullslast(desc(order_subq.c.max_return_time)),
desc(order_subq.c.borrow_no)] # 同一时刻的稳定兜底
if status == 'returned' else
[case((order_subq.c.has_finite == 0, 1), else_=0).asc(),
nullslast(asc(order_subq.c.sort_key)),
# ★ 无限期梯队内按借出时间**从近到远**desc
# 原实现是 asc「借出越久越靠前」设计意图是暴露呆滞借用
# 业务方明确要求改为从近到远,故反转。
desc(order_subq.c.min_borrow_time)]
)
borrow_no_q = borrow_no_q.order_by(*_order_by)
# 分页(基准 = borrow_no 单号数)
pagination = borrow_no_q.paginate(page=page, per_page=limit, error_out=False)