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;
+}