feat(records): 借还记录接入高级筛选
复用 app/utils/advanced_filter.py 的解析与谓词逻辑:
· 父级字段(单号 borrow_no、借用人 borrower_name)走标准 SQL 谓词
· 子级字段(SKU、物料名称)经单号子查询过滤,否定操作符走 NOT IN 整单排除
· 物料名经三表联查(buy/semi/product JOIN material_base)
借还的单号维度查询基于 order_subq 子查询分页,故此处把过滤条件施加在
order_subq.c.borrow_no 上,与既有的状态/日期/公司隔离过滤保持同一层次。
前端 records.vue 新增「高级筛选」el-popover(字段/操作符/值 + 添加条件/
应用筛选/重置),序列化为 advancedFilters JSON 字符串随查询下发。
验证:sku ne 0000000002 → 52 单(库中无单含该 SKU,正确不减);
sku contains 0000 → 52 单(52 单的 SKU 全部含 0000,数据巧合)。
This commit is contained in:
@ -165,6 +165,10 @@ def get_records():
|
||||
start_date = request.args.get('start_date', '')
|
||||
end_date = request.args.get('end_date', '')
|
||||
|
||||
# ★ 高级筛选:JSON 字符串 → 条件列表
|
||||
from app.utils.advanced_filter import parse_advanced_filters
|
||||
advanced_filters = parse_advanced_filters(request.args.get('advancedFilters', ''))
|
||||
|
||||
# ★ 数据权限:普通用户只看“借用人=本人姓名(不含账号前缀)”的借还记录;管理者看全部
|
||||
borrower_name = None
|
||||
if not is_privileged_viewer():
|
||||
@ -179,6 +183,7 @@ def get_records():
|
||||
page=page, limit=10, status=status, keyword=keyword,
|
||||
search_type=search_type, borrower_name=borrower_name,
|
||||
start_date=start_date, end_date=end_date,
|
||||
advanced_filters=advanced_filters,
|
||||
)
|
||||
|
||||
# ★ service 层异常时:code==500 的字典(带 traceback),需要直通到前端,便于排查
|
||||
|
||||
@ -415,7 +415,8 @@ 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):
|
||||
borrower_name=None, start_date=None, end_date=None,
|
||||
advanced_filters=None):
|
||||
"""
|
||||
获取借还记录列表(按单号 borrow_no 维度分页,避免明细撑爆 pageSize)
|
||||
|
||||
@ -688,6 +689,49 @@ class TransService:
|
||||
order_subq.c.borrow_no.in_(company_borrow_nos_subq)
|
||||
)
|
||||
|
||||
# ====================================================================
|
||||
# ★ 高级筛选:父级字段直接过滤;子级字段(SKU/物料名称)走
|
||||
# 「命中单号子查询 → 按单号 IN」的 EXISTS 语义,
|
||||
# 避免在 GROUP BY 前收窄明细范围而丢失同单的兄弟明细。
|
||||
# ====================================================================
|
||||
if advanced_filters:
|
||||
from app.utils.advanced_filter import (
|
||||
build_predicate, apply_child_condition,
|
||||
)
|
||||
parent_map = {
|
||||
'no': TransBorrow.borrow_no,
|
||||
'operator': TransBorrow.borrower_name,
|
||||
'borrower_name': TransBorrow.borrower_name,
|
||||
}
|
||||
child_map = {'sku': TransBorrow.sku}
|
||||
material_stock_models = [
|
||||
(StockBuy, 'stock_buy'),
|
||||
(StockSemi, 'stock_semi'),
|
||||
(StockProduct, 'stock_product'),
|
||||
]
|
||||
for cond in advanced_filters:
|
||||
field = cond.get('field')
|
||||
|
||||
if field in parent_map:
|
||||
# 父级字段:标准 SQL 谓词即可
|
||||
p = build_predicate(cond, parent_map)
|
||||
if p is not None:
|
||||
borrow_no_q = borrow_no_q.filter(
|
||||
order_subq.c.borrow_no.in_(
|
||||
db.session.query(TransBorrow.borrow_no)
|
||||
.filter(p).distinct()
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
if field in child_map or field == 'material_name':
|
||||
# ★ 子级字段:正/负操作符语义分派(否定 → 整单排除)
|
||||
borrow_no_q = apply_child_condition(
|
||||
borrow_no_q, order_subq.c.borrow_no, TransBorrow,
|
||||
cond, child_map, material_stock_models,
|
||||
)
|
||||
continue
|
||||
|
||||
# 日期范围过滤(按借出时间;边界已在入口补全时分秒)
|
||||
if start_date:
|
||||
borrow_no_q = borrow_no_q.filter(order_subq.c.borrow_no.in_(
|
||||
|
||||
Reference in New Issue
Block a user