feat(records): 高级筛选引擎 + 出库记录接入
一、新增共享工具 app/utils/advanced_filter.py
系统内已有该模式(material/list.vue、stock/inbound/buy.vue),
沿用其既有约定:参数名 advancedFilters、值为 JSON 字符串、
操作符 eq/ne/contains/not_contains/ge/le。
· parse_advanced_filters() 解析并规整,坏输入退化为空列表不影响主查询
· build_predicate() 单条件 → SQLAlchemy 谓词,未登记字段返回 None 杜绝列注入
· build_material_name_select() 物料名三表联查(buy/semi/product JOIN material_base)
二、★ 父子关系处理(本次核心)
记录接口返回的是**按单号分组的订单**,而用户筛选字段多落在**明细行**上。
若直接 .filter(TransOutbound.sku.ilike(...)),会在 GROUP BY 前收窄明细范围,
展开行里的兄弟明细会凭空消失。正确做法是先求「含匹配明细的单号集合」
再让主查询按单号 IN 过滤。
实测对照(单 OUT-20260811-1519-0003,21 条明细):
按其中一条 SKU 筛选 → 子查询法保住全部 21 条;直接 filter 只剩 1 条。
三、★ 否定操作符语义(NOT IN)
子级字段的 ne / not_contains 不能直接用 SQL != / NOT LIKE —— 那表达的是
「本单存在某条不等于 X 的明细」,多明细单几乎必然成立,等于筛选失效。
用户意图是**整单排除**,故 apply_child_condition() 统一:
肯定 → order_no IN (含匹配明细的单号)
否定 → order_no NOT IN (含匹配明细的单号)
两者子查询完全一致(都用肯定形式谓词),仅外层取反。
父级字段(单号/操作人)仍走标准 SQL 谓词,语义无歧义。
四、出库记录接入(前端弹窗 + 后端接线)
验证:
eq 0000000002 → 1 单;material_name contains 白板 → 16 单
sku ne 0000000002 → 394 = 395-1,含该 SKU 的单被整体排除
material_name not_contains 白板 → 379 = 395-16
This commit is contained in:
@ -175,6 +175,12 @@ def get_outbound_list():
|
||||
search_type = request.args.get('search_type', 'all')
|
||||
company = request.args.get('company', '')
|
||||
|
||||
# ★ 高级筛选:JSON 字符串 → 条件列表(解析失败退化为空,不影响主查询)
|
||||
from app.utils.advanced_filter import parse_advanced_filters
|
||||
advanced_filters = parse_advanced_filters(
|
||||
request.args.get('advancedFilters', '')
|
||||
)
|
||||
|
||||
# ★ 数据权限:普通用户只看“领用人=本人姓名(不含账号前缀)”的出库记录;管理者看全部
|
||||
consumer_name = None
|
||||
if not is_privileged_viewer():
|
||||
@ -189,7 +195,8 @@ def get_outbound_list():
|
||||
# ★ [修改] 调用分组查询服务,支持搜索类型
|
||||
result = OutboundService.get_grouped_list(
|
||||
page, limit, keyword, search_type=search_type,
|
||||
company=company, consumer_name=consumer_name
|
||||
company=company, consumer_name=consumer_name,
|
||||
advanced_filters=advanced_filters,
|
||||
)
|
||||
|
||||
# 字段级脱敏
|
||||
|
||||
Reference in New Issue
Block a user