diff --git a/inventory-backend/app/api/v1/transactions.py b/inventory-backend/app/api/v1/transactions.py index ef5c60f..07c53fc 100644 --- a/inventory-backend/app/api/v1/transactions.py +++ b/inventory-backend/app/api/v1/transactions.py @@ -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),需要直通到前端,便于排查 diff --git a/inventory-backend/app/services/trans_service.py b/inventory-backend/app/services/trans_service.py index 78c8172..3684aed 100644 --- a/inventory-backend/app/services/trans_service.py +++ b/inventory-backend/app/services/trans_service.py @@ -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_( diff --git a/inventory-web/src/views/transaction/records.vue b/inventory-web/src/views/transaction/records.vue index 6e9baf3..cb91395 100644 --- a/inventory-web/src/views/transaction/records.vue +++ b/inventory-web/src/views/transaction/records.vue @@ -47,6 +47,43 @@ 查询 重置 + + + + +
+
+ + + + + + + + 删除 +
+
+ 添加条件 + 应用筛选 + 重置 +
+
+
@@ -287,6 +324,47 @@ const dateRange = ref([]) const page = ref(1) const loading = ref(false) +// --- ★ 高级筛选 --- +const advancedFilterVisible = ref(false) +const advancedConditions = ref([{ field: '', operator: '', value: '' }]) +const appliedConditions = ref([]) +const advancedFilters = ref([]) +const fieldOptions = [ + { value: 'no', label: '单号' }, + { value: 'sku', label: 'SKU' }, + { value: 'material_name', label: '物料名称' }, + { value: 'borrower_name', label: '操作人' }, +] +const operatorOptions = [ + { value: 'contains', label: '包含' }, + { value: 'eq', label: '等于' }, + { value: 'not_contains', label: '不包含' }, + { value: 'ne', label: '不等于' }, +] + +const addCondition = () => { + advancedConditions.value.push({ field: '', operator: '', value: '' }) +} +const removeCondition = (index: number) => { + advancedConditions.value.splice(index, 1) +} +const applyAdvancedFilter = () => { + const valid = advancedConditions.value.filter(c => c.field && c.operator && c.value !== '') + advancedFilters.value = valid + appliedConditions.value = valid + advancedFilterVisible.value = false + page.value = 1 + fetchData() +} +const resetAdvancedFilter = () => { + advancedConditions.value = [{ field: '', operator: '', value: '' }] + advancedFilters.value = [] + appliedConditions.value = [] + advancedFilterVisible.value = false + page.value = 1 + fetchData() +} + // ★ 借库报废相关 const canScrap = computed(() => userStore.role === 'SUPER_ADMIN' || @@ -355,7 +433,9 @@ const fetchData = async () => { page: page.value, status: status.value, keyword: keyword.value, - search_type: searchType.value + search_type: searchType.value, + // ★ 高级筛选:后端约定参数名为 advancedFilters,值为 JSON 字符串 + advancedFilters: JSON.stringify(advancedFilters.value || []), } if (dateRange.value && dateRange.value.length === 2) { params.start_date = dateRange.value[0] @@ -411,6 +491,9 @@ const resetFilter = () => { keyword.value = '' searchType.value = 'all' dateRange.value = [] + advancedFilters.value = [] + advancedConditions.value = [{ field: '', operator: '', value: '' }] + appliedConditions.value = [] page.value = 1 fetchData() } @@ -473,4 +556,11 @@ onBeforeUnmount(() => { margin-bottom: 0; margin-right: 12px; } + +/* 高级筛选条件行 */ +.condition-row { + display: flex; + align-items: center; + margin-bottom: 10px; +}