feat: add table sorting and advanced filtering for products
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -78,7 +78,40 @@ def get_list():
|
||||
category = request.args.get('category', '')
|
||||
material_type = request.args.get('material_type', '')
|
||||
company = request.args.get('company', '')
|
||||
result = ProductInboundService.get_list(page, limit, keyword, statuses, category, material_type, company)
|
||||
order_by_column = request.args.get('orderByColumn', '')
|
||||
is_asc = request.args.get('isAsc', '')
|
||||
advanced_filters_str = request.args.get('advancedFilters', '')
|
||||
|
||||
# 准备额外筛选字典
|
||||
extra_filters = {}
|
||||
if company:
|
||||
extra_filters['company'] = company
|
||||
if category:
|
||||
extra_filters['category'] = category
|
||||
if material_type:
|
||||
extra_filters['material_type'] = material_type
|
||||
if order_by_column:
|
||||
extra_filters['order_by_column'] = order_by_column
|
||||
if is_asc:
|
||||
extra_filters['is_asc'] = is_asc
|
||||
if advanced_filters_str:
|
||||
try:
|
||||
import json
|
||||
advanced_filters = json.loads(advanced_filters_str)
|
||||
extra_filters['advanced_filters'] = advanced_filters
|
||||
except Exception:
|
||||
extra_filters['advanced_filters'] = []
|
||||
|
||||
# 调用服务,传入所有参数
|
||||
result = ProductInboundService.get_list(
|
||||
page, limit, keyword, statuses,
|
||||
category=extra_filters.get('category'),
|
||||
material_type=extra_filters.get('material_type'),
|
||||
company=extra_filters.get('company'),
|
||||
order_by_column=extra_filters.get('order_by_column'),
|
||||
is_asc=extra_filters.get('is_asc'),
|
||||
advanced_filters=extra_filters.get('advanced_filters')
|
||||
)
|
||||
user_permissions = get_current_user_permissions()
|
||||
if result.get('items'):
|
||||
result['items'] = [filter_item_by_permissions(item, user_permissions) for item in result['items']]
|
||||
@ -184,4 +217,4 @@ def calculate_bom_cost():
|
||||
return jsonify({"code": 200, "msg": "success", "data": cost})
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||
return jsonify({"code": 500, "msg": str(e)}), 500
|
||||
|
||||
@ -270,7 +270,8 @@ class ProductInboundService:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None):
|
||||
def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None,
|
||||
order_by_column=None, is_asc=None, advanced_filters=None):
|
||||
from app.models.inbound.product import StockProduct
|
||||
try:
|
||||
query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id)
|
||||
@ -304,8 +305,118 @@ class ProductInboundService:
|
||||
StockProduct.stock_quantity > 0
|
||||
)
|
||||
)
|
||||
pagination = query.order_by(StockProduct.production_date.desc()).paginate(page=page, per_page=limit,
|
||||
error_out=False)
|
||||
|
||||
# 高级动态筛选
|
||||
if advanced_filters:
|
||||
if isinstance(advanced_filters, str):
|
||||
try:
|
||||
import json
|
||||
advanced_filters = json.loads(advanced_filters)
|
||||
except:
|
||||
advanced_filters = []
|
||||
if isinstance(advanced_filters, list):
|
||||
field_mapping = {
|
||||
'id': StockProduct.id,
|
||||
'base_id': StockProduct.base_id,
|
||||
'company_name': MaterialBase.company_name,
|
||||
'material_name': MaterialBase.name,
|
||||
'spec_model': MaterialBase.spec_model,
|
||||
'category': MaterialBase.category,
|
||||
'material_type': MaterialBase.material_type,
|
||||
'unit': MaterialBase.unit,
|
||||
'sku': StockProduct.sku,
|
||||
'inbound_date': StockProduct.production_date,
|
||||
'barcode': StockProduct.barcode,
|
||||
'serial_number': StockProduct.serial_number,
|
||||
'batch_number': StockProduct.serial_number,
|
||||
'status': StockProduct.status,
|
||||
'quality_status': StockProduct.quality_status,
|
||||
'in_quantity': StockProduct.in_quantity,
|
||||
'stock_quantity': StockProduct.stock_quantity,
|
||||
'available_quantity': StockProduct.available_quantity,
|
||||
'warehouse_location': StockProduct.warehouse_location,
|
||||
'bom_code': StockProduct.bom_code,
|
||||
'bom_version': StockProduct.bom_version,
|
||||
'work_order_code': StockProduct.work_order_code,
|
||||
'raw_material_cost': StockProduct.raw_material_cost,
|
||||
'unit_total_cost': StockProduct.manual_cost,
|
||||
'order_id': StockProduct.order_id,
|
||||
'sale_price': StockProduct.sale_price,
|
||||
'production_manager': StockProduct.production_manager,
|
||||
}
|
||||
for cond in advanced_filters:
|
||||
field = cond.get('field')
|
||||
operator = cond.get('operator')
|
||||
value = cond.get('value')
|
||||
if not field or not operator:
|
||||
continue
|
||||
model_field = field_mapping.get(field)
|
||||
if model_field is None:
|
||||
continue
|
||||
# 防止 SQL 注入,只允许映射的字段
|
||||
if operator == '=':
|
||||
query = query.filter(model_field == value)
|
||||
elif operator == '!=':
|
||||
query = query.filter(model_field != value)
|
||||
elif operator == 'like':
|
||||
query = query.filter(model_field.ilike(f'%{value}%'))
|
||||
elif operator == 'not_like':
|
||||
query = query.filter(~model_field.ilike(f'%{value}%'))
|
||||
elif operator == '>':
|
||||
if value.replace('.', '', 1).isdigit():
|
||||
query = query.filter(model_field > float(value))
|
||||
elif operator == '<':
|
||||
if value.replace('.', '', 1).isdigit():
|
||||
query = query.filter(model_field < float(value))
|
||||
elif operator == '>=':
|
||||
if value.replace('.', '', 1).isdigit():
|
||||
query = query.filter(model_field >= float(value))
|
||||
elif operator == '<=':
|
||||
if value.replace('.', '', 1).isdigit():
|
||||
query = query.filter(model_field <= float(value))
|
||||
|
||||
# 动态排序
|
||||
order_field = None
|
||||
if order_by_column and is_asc is not None:
|
||||
order_mapping = {
|
||||
'id': StockProduct.id,
|
||||
'base_id': StockProduct.base_id,
|
||||
'company_name': MaterialBase.company_name,
|
||||
'material_name': MaterialBase.name,
|
||||
'category': MaterialBase.category,
|
||||
'material_type': MaterialBase.material_type,
|
||||
'spec_model': MaterialBase.spec_model,
|
||||
'unit': MaterialBase.unit,
|
||||
'sku': StockProduct.sku,
|
||||
'inbound_date': StockProduct.production_date,
|
||||
'barcode': StockProduct.barcode,
|
||||
'serial_number': StockProduct.serial_number,
|
||||
'batch_number': StockProduct.serial_number,
|
||||
'status': StockProduct.status,
|
||||
'quality_status': StockProduct.quality_status,
|
||||
'in_quantity': StockProduct.in_quantity,
|
||||
'stock_quantity': StockProduct.stock_quantity,
|
||||
'available_quantity': StockProduct.available_quantity,
|
||||
'warehouse_location': StockProduct.warehouse_location,
|
||||
'bom_code': StockProduct.bom_code,
|
||||
'bom_version': StockProduct.bom_version,
|
||||
'work_order_code': StockProduct.work_order_code,
|
||||
'raw_material_cost': StockProduct.raw_material_cost,
|
||||
'unit_total_cost': StockProduct.manual_cost,
|
||||
'order_id': StockProduct.order_id,
|
||||
'sale_price': StockProduct.sale_price,
|
||||
'production_manager': StockProduct.production_manager,
|
||||
}
|
||||
order_field = order_mapping.get(order_by_column)
|
||||
if order_field is not None:
|
||||
if is_asc == 'true' or is_asc == True:
|
||||
query = query.order_by(order_field.asc())
|
||||
else:
|
||||
query = query.order_by(order_field.desc())
|
||||
if order_field is None:
|
||||
query = query.order_by(StockProduct.production_date.desc())
|
||||
|
||||
pagination = query.paginate(page=page, per_page=limit, error_out=False)
|
||||
current_items = pagination.items
|
||||
|
||||
def parse_img(json_str):
|
||||
@ -321,7 +432,7 @@ class ProductInboundService:
|
||||
item_dict['unit_total_cost'] = float(item.manual_cost or 0)
|
||||
items.append(item_dict)
|
||||
return {"total": pagination.total, "items": items}
|
||||
except:
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return {"total": 0, "items": []}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user