from flask import Blueprint, request, jsonify from app.services.inbound.buy_service import BuyInboundService from app.utils.decorators import permission_required import traceback inbound_buy_bp = Blueprint('stock_buy', __name__) # ============================================================================== # 辅助函数:获取当前用户的完整权限列表(基于角色查询) # ============================================================================== def get_current_user_permissions(): """ 返回当前用户拥有的所有权限码列表(包括菜单和元素) 此函数根据角色查询数据库得到权限。 """ from flask_jwt_extended import get_jwt from app.services.auth_service import AuthService claims = get_jwt() user_role = claims.get('role') if not user_role: return [] # 超级管理员返回所有字段权限 (忽略大小写) if user_role.upper() == 'SUPER_ADMIN': # 返回所有以 inbound_buy: 开头的权限码(这里我们返回一个特殊标记,表示全部) # 为了简单,我们返回 ['inbound_buy:*'],在过滤函数中特殊处理 return ['inbound_buy:*'] perm_dict = AuthService.get_user_permissions(user_role) # 合并菜单和元素权限 perms = perm_dict.get('menus', []) + perm_dict.get('elements', []) return perms def filter_item_by_permissions(item_dict, user_permissions): """ 根据用户权限过滤 item 字典,无权限的字段值置为 None """ # 字段名到权限码的映射(与前端 permissionMap 保持一致) field_to_perm = { 'id': 'inbound_buy:id', 'base_id': 'inbound_buy:base_id', 'global_print_id': 'inbound_buy:global_print_id', 'sku': 'inbound_buy:sku', 'barcode': 'inbound_buy:barcode', 'in_date': 'inbound_buy:in_date', 'serial_number': 'inbound_buy:serial_number', 'batch_number': 'inbound_buy:batch_number', 'status': 'inbound_buy:status', 'in_quantity': 'inbound_buy:in_quantity', 'stock_quantity': 'inbound_buy:stock_quantity', 'available_quantity': 'inbound_buy:available_quantity', 'inspection_status': 'inbound_buy:inspection_status', 'warehouse_location': 'inbound_buy:warehouse_location', 'unit_price': 'inbound_buy:unit_price', 'post_tax_unit_price': 'inbound_buy:post_tax_unit_price', 'tax_rate': 'inbound_buy:tax_rate', 'total_price': 'inbound_buy:total_price', 'currency': 'inbound_buy:currency', 'exchange_rate': 'inbound_buy:exchange_rate', 'supplier_name': 'inbound_buy:supplier_name', 'buyer_name': 'inbound_buy:buyer_name', 'buyer_email': 'inbound_buy:buyer_email', 'original_link': 'inbound_buy:original_link', 'detail_link': 'inbound_buy:detail_link', 'arrival_photo': 'inbound_buy:arrival_photo', 'inspection_report': 'inbound_buy:inspection_report', 'material_name': 'inbound_buy:material_name', 'spec_model': 'inbound_buy:spec_model', 'category': 'inbound_buy:category', 'unit': 'inbound_buy:unit', 'material_type': 'inbound_buy:material_type', 'company_name': 'inbound_buy:company_name', } # 如果用户是超级管理员且有 'inbound_buy:*',则不过滤 if 'inbound_buy:*' in user_permissions: return item_dict for field, perm_code in field_to_perm.items(): if field in item_dict and perm_code not in user_permissions: item_dict[field] = None return item_dict # ------------------------------------------------------------------ # 0. 基础物料搜索 # ------------------------------------------------------------------ @inbound_buy_bp.route('/search-base', methods=['GET']) @permission_required('inbound_buy') def search_base(): try: keyword = request.args.get('keyword', '') page = request.args.get('page', 1, type=int) # 固定每次加载50条 limit = 50 result = BuyInboundService.search_base_material(keyword, page, limit) return jsonify({ "code": 200, "msg": "success", "data": result['items'], "total": result['total'], "has_next": result['has_next'] }) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 # ------------------------------------------------------------------ # 1. 获取列表 (修改:接收 category, material_type, orderByColumn, isAsc, advancedFilters) # ------------------------------------------------------------------ @inbound_buy_bp.route('/list', methods=['GET']) @permission_required('inbound_buy') def get_list(): try: page = request.args.get('page', 1, type=int) limit = request.args.get('pageSize', 15, type=int) keyword = request.args.get('keyword', '') # 新增筛选参数 category = request.args.get('category', '') material_type = request.args.get('material_type', '') company = request.args.get('company', '') # 排序参数 order_by = request.args.get('orderByColumn', '').strip() is_asc = request.args.get('isAsc', '').strip() # 高级筛选参数 advanced_filters_raw = request.args.get('advancedFilters', '[]') import json try: advanced_filters = json.loads(advanced_filters_raw) if advanced_filters_raw else [] except json.JSONDecodeError: advanced_filters = [] # 状态参数处理 statuses_str = request.args.get('statuses', '') statuses = statuses_str.split(',') if statuses_str else [] result = BuyInboundService.get_list(page, limit, keyword, statuses, category, material_type, company, order_by, is_asc, 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']] return jsonify({"code": 200, "msg": "success", "data": result}) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 # ------------------------------------------------------------------ # 2. 新增入库 # ------------------------------------------------------------------ @inbound_buy_bp.route('/submit', methods=['POST']) @permission_required('inbound_buy:operation') def submit(): try: data = request.get_json() if not data: return jsonify({"code": 400, "msg": "No data"}), 400 # 数据清洗:移除用户没有权限的字段 user_permissions = get_current_user_permissions() # 超级管理员不过滤 if 'inbound_buy:*' not in user_permissions: # 字段名到权限码的映射(与前端 permissionMap 保持一致) field_to_perm = { 'id': 'inbound_buy:id', 'base_id': 'inbound_buy:base_id', 'global_print_id': 'inbound_buy:global_print_id', 'sku': 'inbound_buy:sku', 'barcode': 'inbound_buy:barcode', 'in_date': 'inbound_buy:in_date', 'serial_number': 'inbound_buy:serial_number', 'batch_number': 'inbound_buy:batch_number', 'status': 'inbound_buy:status', 'in_quantity': 'inbound_buy:in_quantity', 'stock_quantity': 'inbound_buy:stock_quantity', 'available_quantity': 'inbound_buy:available_quantity', 'inspection_status': 'inbound_buy:inspection_status', 'warehouse_location': 'inbound_buy:warehouse_location', 'unit_price': 'inbound_buy:unit_price', 'post_tax_unit_price': 'inbound_buy:post_tax_unit_price', 'tax_rate': 'inbound_buy:tax_rate', 'total_price': 'inbound_buy:total_price', 'currency': 'inbound_buy:currency', 'exchange_rate': 'inbound_buy:exchange_rate', 'supplier_name': 'inbound_buy:supplier_name', 'buyer_name': 'inbound_buy:buyer_name', 'buyer_email': 'inbound_buy:buyer_email', 'original_link': 'inbound_buy:original_link', 'detail_link': 'inbound_buy:detail_link', 'arrival_photo': 'inbound_buy:arrival_photo', 'inspection_report': 'inbound_buy:inspection_report', 'material_name': 'inbound_buy:material_name', 'spec_model': 'inbound_buy:spec_model', 'category': 'inbound_buy:category', 'unit': 'inbound_buy:unit', 'material_type': 'inbound_buy:material_type', 'company_name': 'inbound_buy:company_name', } # 复制一份,避免遍历时修改字典 for field in list(data.keys()): perm_code = field_to_perm.get(field) if perm_code and perm_code not in user_permissions: data.pop(field, None) new_stock = BuyInboundService.handle_inbound(data) return jsonify({ "code": 200, "msg": "入库成功", "data": new_stock.to_dict() }) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 # ------------------------------------------------------------------ # 3. 更新入库 # ------------------------------------------------------------------ @inbound_buy_bp.route('/', methods=['PUT']) @permission_required('inbound_buy:operation') def update_buy(id): try: data = request.get_json() # 数据清洗:移除用户没有权限的字段 user_permissions = get_current_user_permissions() # 超级管理员不过滤 if 'inbound_buy:*' not in user_permissions: field_to_perm = { 'id': 'inbound_buy:id', 'base_id': 'inbound_buy:base_id', 'global_print_id': 'inbound_buy:global_print_id', 'sku': 'inbound_buy:sku', 'barcode': 'inbound_buy:barcode', 'in_date': 'inbound_buy:in_date', 'serial_number': 'inbound_buy:serial_number', 'batch_number': 'inbound_buy:batch_number', 'status': 'inbound_buy:status', 'in_quantity': 'inbound_buy:in_quantity', 'stock_quantity': 'inbound_buy:stock_quantity', 'available_quantity': 'inbound_buy:available_quantity', 'inspection_status': 'inbound_buy:inspection_status', 'warehouse_location': 'inbound_buy:warehouse_location', 'unit_price': 'inbound_buy:unit_price', 'post_tax_unit_price': 'inbound_buy:post_tax_unit_price', 'tax_rate': 'inbound_buy:tax_rate', 'total_price': 'inbound_buy:total_price', 'currency': 'inbound_buy:currency', 'exchange_rate': 'inbound_buy:exchange_rate', 'supplier_name': 'inbound_buy:supplier_name', 'buyer_name': 'inbound_buy:buyer_name', 'buyer_email': 'inbound_buy:buyer_email', 'original_link': 'inbound_buy:original_link', 'detail_link': 'inbound_buy:detail_link', 'arrival_photo': 'inbound_buy:arrival_photo', 'inspection_report': 'inbound_buy:inspection_report', 'material_name': 'inbound_buy:material_name', 'spec_model': 'inbound_buy:spec_model', 'category': 'inbound_buy:category', 'unit': 'inbound_buy:unit', 'material_type': 'inbound_buy:material_type', 'company_name': 'inbound_buy:company_name', } # 复制一份,避免遍历时修改字典 for field in list(data.keys()): perm_code = field_to_perm.get(field) if perm_code and perm_code not in user_permissions: data.pop(field, None) BuyInboundService.update_inbound(id, data) return jsonify({"code": 200, "msg": "更新成功"}) except Exception as e: return jsonify({"code": 500, "msg": str(e)}), 500 # ------------------------------------------------------------------ # 4. 删除 # ------------------------------------------------------------------ @inbound_buy_bp.route('/', methods=['DELETE']) @permission_required('inbound_buy:operation') def delete_buy(id): try: BuyInboundService.delete_inbound(id) return jsonify({"code": 200, "msg": "删除成功"}) except Exception as e: return jsonify({"code": 500, "msg": str(e)}), 500 # ------------------------------------------------------------------ # 5. [新增] 获取筛选下拉选项 (修复404的关键) # ------------------------------------------------------------------ @inbound_buy_bp.route('/options', methods=['GET']) @permission_required('inbound_buy') def get_options(): try: data = BuyInboundService.get_filter_options() return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: return jsonify({"code": 500, "msg": str(e)}), 500 # ------------------------------------------------------------------ # 6. 获取关联的出库历史 (如果有) # ------------------------------------------------------------------ @inbound_buy_bp.route('//history', methods=['GET']) @permission_required('inbound_buy') def get_history(id): # 如果没有出库模块,这个接口可能为空,但为保持兼容性保留 return jsonify({"code": 200, "msg": "success", "data": []}) # ------------------------------------------------------------------ # 7. 供应商建议 # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/suppliers', methods=['GET']) @permission_required('inbound_buy') def get_supplier_suggestions(): base_id = request.args.get('base_id', type=int) if not base_id: return jsonify({"code": 400, "msg": "base_id required"}), 400 data = BuyInboundService.get_history_suppliers(base_id) return jsonify({"code": 200, "msg": "success", "data": data}) # ------------------------------------------------------------------ # 8. 采购人建议 (全局) # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/users', methods=['GET']) @permission_required('inbound_buy') def get_user_suggestions(): keyword = request.args.get('keyword', '') data = BuyInboundService.get_history_purchasers(keyword) return jsonify({"code": 200, "msg": "success", "data": data}) # ------------------------------------------------------------------ # 9. 链接建议 # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/links', methods=['GET']) @permission_required('inbound_buy') def get_link_suggestions(): base_id = request.args.get('base_id', type=int) link_type = request.args.get('type', 'original') # original or detail if not base_id: return jsonify({"code": 400, "msg": "base_id required"}), 400 data = BuyInboundService.get_history_links(base_id, link_type) return jsonify({"code": 200, "msg": "success", "data": data}) # ------------------------------------------------------------------ # 10. 库位建议 # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/locations', methods=['GET']) @permission_required('inbound_buy') def get_location_suggestions(): base_id = request.args.get('base_id', type=int) if not base_id: return jsonify({"code": 400, "msg": "base_id required"}), 400 data = BuyInboundService.get_history_locations(base_id) return jsonify({"code": 200, "msg": "success", "data": data})