From e61c179d77d3b356f2033ec8e8a98085569ef6ec Mon Sep 17 00:00:00 2001 From: dxc Date: Sat, 28 Feb 2026 17:27:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8D=8A=E6=88=90=E5=93=81?= =?UTF-8?q?=E5=92=8C=E6=88=90=E5=93=81=E6=96=B0=E5=A2=9E=E6=97=B6=E5=80=99?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E4=B8=8B=E6=8B=89=E6=A1=86=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=B4=9F=E8=B4=A3?= =?UTF-8?q?=E4=BA=BA=E5=92=8C=E7=94=9F=E4=BA=A7=E4=BA=BA=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/api/v1/inbound/product.py | 250 ++------------ inventory-backend/app/api/v1/inbound/semi.py | 304 ++++-------------- .../app/services/inbound/product_service.py | 12 +- .../app/services/inbound/semi_service.py | 12 +- .../src/views/stock/inbound/product.vue | 21 +- .../src/views/stock/inbound/semi.vue | 13 +- 6 files changed, 116 insertions(+), 496 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/product.py b/inventory-backend/app/api/v1/inbound/product.py index 51ab9eb..bfbcbfc 100644 --- a/inventory-backend/app/api/v1/inbound/product.py +++ b/inventory-backend/app/api/v1/inbound/product.py @@ -4,132 +4,68 @@ from app.services.inbound.product_service import ProductInboundService from app.utils.decorators import permission_required import traceback +# === 这一行非常关键,绝对不能丢!=== inbound_product_bp = Blueprint('stock_product', __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_product: 开头的权限码(这里我们返回一个特殊标记,表示全部) - # 为了简单,我们返回 ['inbound_product:*'],在过滤函数中特殊处理 - return ['inbound_product:*'] + if not user_role: return [] + if user_role.upper() == 'SUPER_ADMIN': return ['inbound_product:*'] perm_dict = AuthService.get_user_permissions(user_role) - # 合并菜单和元素权限 - perms = perm_dict.get('menus', []) + perm_dict.get('elements', []) - return perms - + return perm_dict.get('menus', []) + perm_dict.get('elements', []) def filter_item_by_permissions(item_dict, user_permissions): - """ - 根据用户权限过滤 item 字典,无权限的字段值置为 None - """ - # 字段名到权限码的映射(与前端 permissionMap 保持一致) field_to_perm = { - 'id': 'inbound_product:id', - 'base_id': 'inbound_product:base_id', - 'company_name': 'inbound_product:company_name', - 'material_name': 'inbound_product:material_name', - 'category': 'inbound_product:category', - 'material_type': 'inbound_product:material_type', - 'spec_model': 'inbound_product:spec_model', - 'unit': 'inbound_product:unit', - 'sku': 'inbound_product:sku', - 'inbound_date': 'inbound_product:inbound_date', - 'barcode': 'inbound_product:barcode', - 'serial_number': 'inbound_product:serial_number', - 'status': 'inbound_product:status', - 'quality_status': 'inbound_product:quality_status', - 'in_quantity': 'inbound_product:in_quantity', - 'stock_quantity': 'inbound_product:stock_quantity', - 'available_quantity': 'inbound_product:available_quantity', - 'warehouse_location': 'inbound_product:warehouse_location', - 'bom_code': 'inbound_product:bom_code', - 'bom_version': 'inbound_product:bom_version', - 'work_order_code': 'inbound_product:work_order_code', - 'order_id': 'inbound_product:order_id', - 'production_manager': 'inbound_product:production_manager', - 'production_start_time': 'inbound_product:production_start_time', - 'production_end_time': 'inbound_product:production_end_time', - 'raw_material_cost': 'inbound_product:raw_material_cost', - 'manual_cost': 'inbound_product:manual_cost', - 'sale_price': 'inbound_product:sale_price', - 'product_photo': 'inbound_product:product_photo', - 'quality_report_link': 'inbound_product:quality_report_link', - 'inspection_report_link': 'inbound_product:inspection_report_link', - 'detail_link': 'inbound_product:detail_link', + 'id': 'inbound_product:id', 'base_id': 'inbound_product:base_id', 'company_name': 'inbound_product:company_name', + 'material_name': 'inbound_product:material_name', 'category': 'inbound_product:category', + 'material_type': 'inbound_product:material_type', 'spec_model': 'inbound_product:spec_model', + 'unit': 'inbound_product:unit', 'sku': 'inbound_product:sku', 'inbound_date': 'inbound_product:inbound_date', + 'barcode': 'inbound_product:barcode', 'serial_number': 'inbound_product:serial_number', + 'status': 'inbound_product:status', 'quality_status': 'inbound_product:quality_status', + 'in_quantity': 'inbound_product:in_quantity', 'stock_quantity': 'inbound_product:stock_quantity', + 'available_quantity': 'inbound_product:available_quantity', 'warehouse_location': 'inbound_product:warehouse_location', + 'bom_code': 'inbound_product:bom_code', 'bom_version': 'inbound_product:bom_version', + 'work_order_code': 'inbound_product:work_order_code', 'order_id': 'inbound_product:order_id', + 'production_manager': 'inbound_product:production_manager', 'production_start_time': 'inbound_product:production_start_time', + 'production_end_time': 'inbound_product:production_end_time', 'raw_material_cost': 'inbound_product:raw_material_cost', + 'manual_cost': 'inbound_product:manual_cost', 'sale_price': 'inbound_product:sale_price', + 'product_photo': 'inbound_product:product_photo', 'quality_report_link': 'inbound_product:quality_report_link', + 'inspection_report_link': 'inbound_product:inspection_report_link', 'detail_link': 'inbound_product:detail_link', } - # 如果用户是超级管理员且有 'inbound_product:*',则不过滤 - if 'inbound_product:*' in user_permissions: - return item_dict + if 'inbound_product:*' 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 + if field in item_dict and perm_code not in user_permissions: item_dict[field] = None return item_dict - -# ------------------------------------------------------------------ -# 0. 基础物料搜索 (关键接口:配合 Service 实现自动回填) -# ------------------------------------------------------------------ @inbound_product_bp.route('/search-base', methods=['GET']) @permission_required('inbound_product') def search_base(): - """ - 对应前端 API: /inbound/product/search-base - 功能: 模糊搜索基础物料,返回 spec, unit, category, type 等详细信息 - """ try: keyword = request.args.get('keyword', '') page = request.args.get('page', 1, type=int) - # 调用 Service 层已修复的 search_base_material 方法 result = ProductInboundService.search_base_material(keyword, page) - # 字段级脱敏 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 -# ------------------------------------------------------------------ -# 0.5 [新增] BOM 搜索接口 -# ------------------------------------------------------------------ @inbound_product_bp.route('/search-bom', methods=['GET']) @permission_required('inbound_product') def search_bom(): - """ - 供前端下拉框远程搜索使用 (搜索BOM) - """ try: keyword = request.args.get('keyword', '') data = ProductInboundService.search_bom_options(keyword) - return jsonify({ - "code": 200, - "msg": "success", - "data": data - }) + return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 1. 获取列表 (支持 status 多选筛选) -# ------------------------------------------------------------------ @inbound_product_bp.route('/list', methods=['GET']) @permission_required('inbound_product') def get_list(): @@ -137,21 +73,12 @@ def get_list(): page = request.args.get('page', 1, type=int) limit = request.args.get('pageSize', 15, type=int) keyword = request.args.get('keyword', '') - - # 接收状态参数 (逗号分隔字符串 -> 列表) statuses_str = request.args.get('statuses', '') statuses = statuses_str.split(',') if statuses_str else [] - - # 接收新增筛选参数 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 - ) - # 字段级脱敏 + result = ProductInboundService.get_list(page, limit, keyword, statuses, category, material_type, company) user_permissions = get_current_user_permissions() if result.get('items'): result['items'] = [filter_item_by_permissions(item, user_permissions) for item in result['items']] @@ -160,138 +87,41 @@ def get_list(): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 2. 新增入库 -# ------------------------------------------------------------------ @inbound_product_bp.route('/submit', methods=['POST']) @permission_required('inbound_product:operation') def submit(): try: data = request.get_json() - if not data: - return jsonify({"code": 400, "msg": "No data"}), 400 - - # 数据清洗:移除用户没有权限的字段 + if not data: return jsonify({"code": 400, "msg": "No data"}), 400 user_permissions = get_current_user_permissions() - # 超级管理员不过滤 if 'inbound_product:*' not in user_permissions: - field_to_perm = { - 'id': 'inbound_product:id', - 'base_id': 'inbound_product:base_id', - 'company_name': 'inbound_product:company_name', - 'material_name': 'inbound_product:material_name', - 'category': 'inbound_product:category', - 'material_type': 'inbound_product:material_type', - 'spec_model': 'inbound_product:spec_model', - 'unit': 'inbound_product:unit', - 'sku': 'inbound_product:sku', - 'inbound_date': 'inbound_product:inbound_date', - 'barcode': 'inbound_product:barcode', - 'serial_number': 'inbound_product:serial_number', - 'status': 'inbound_product:status', - 'quality_status': 'inbound_product:quality_status', - 'in_quantity': 'inbound_product:in_quantity', - 'stock_quantity': 'inbound_product:stock_quantity', - 'available_quantity': 'inbound_product:available_quantity', - 'warehouse_location': 'inbound_product:warehouse_location', - 'bom_code': 'inbound_product:bom_code', - 'bom_version': 'inbound_product:bom_version', - 'work_order_code': 'inbound_product:work_order_code', - 'order_id': 'inbound_product:order_id', - 'production_manager': 'inbound_product:production_manager', - 'production_start_time': 'inbound_product:production_start_time', - 'production_end_time': 'inbound_product:production_end_time', - 'raw_material_cost': 'inbound_product:raw_material_cost', - 'manual_cost': 'inbound_product:manual_cost', - 'sale_price': 'inbound_product:sale_price', - 'product_photo': 'inbound_product:product_photo', - 'quality_report_link': 'inbound_product:quality_report_link', - 'inspection_report_link': 'inbound_product:inspection_report_link', - 'detail_link': 'inbound_product:detail_link', - } - # 复制一份,避免遍历时修改字典 + field_to_perm = {'id': 'inbound_product:id', 'base_id': 'inbound_product:base_id', 'company_name': 'inbound_product:company_name', 'material_name': 'inbound_product:material_name', 'category': 'inbound_product:category', 'material_type': 'inbound_product:material_type', 'spec_model': 'inbound_product:spec_model', 'unit': 'inbound_product:unit', 'sku': 'inbound_product:sku', 'inbound_date': 'inbound_product:inbound_date', 'barcode': 'inbound_product:barcode', 'serial_number': 'inbound_product:serial_number', 'status': 'inbound_product:status', 'quality_status': 'inbound_product:quality_status', 'in_quantity': 'inbound_product:in_quantity', 'stock_quantity': 'inbound_product:stock_quantity', 'available_quantity': 'inbound_product:available_quantity', 'warehouse_location': 'inbound_product:warehouse_location', 'bom_code': 'inbound_product:bom_code', 'bom_version': 'inbound_product:bom_version', 'work_order_code': 'inbound_product:work_order_code', 'order_id': 'inbound_product:order_id', 'production_manager': 'inbound_product:production_manager', 'production_start_time': 'inbound_product:production_start_time', 'production_end_time': 'inbound_product:production_end_time', 'raw_material_cost': 'inbound_product:raw_material_cost', 'manual_cost': 'inbound_product:manual_cost', 'sale_price': 'inbound_product:sale_price', 'product_photo': 'inbound_product:product_photo', 'quality_report_link': 'inbound_product:quality_report_link', 'inspection_report_link': 'inbound_product:inspection_report_link', 'detail_link': 'inbound_product:detail_link'} 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) - - # 调用 Service 处理入库,获取新创建的对象 + if perm_code and perm_code not in user_permissions: data.pop(field, None) new_stock = ProductInboundService.handle_inbound(data) - - # 返回成功信息以及新创建的数据(包含生成的ID和SKU),供前端自动打印使用 - return jsonify({ - "code": 200, - "msg": "入库成功", - "data": new_stock.to_dict() - }) + 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_product_bp.route('/', methods=['PUT']) @permission_required('inbound_product:operation') def update(id): try: data = request.get_json() - # 数据清洗:移除用户没有权限的字段 user_permissions = get_current_user_permissions() - # 超级管理员不过滤 if 'inbound_product:*' not in user_permissions: - field_to_perm = { - 'id': 'inbound_product:id', - 'base_id': 'inbound_product:base_id', - 'company_name': 'inbound_product:company_name', - 'material_name': 'inbound_product:material_name', - 'category': 'inbound_product:category', - 'material_type': 'inbound_product:material_type', - 'spec_model': 'inbound_product:spec_model', - 'unit': 'inbound_product:unit', - 'sku': 'inbound_product:sku', - 'inbound_date': 'inbound_product:inbound_date', - 'barcode': 'inbound_product:barcode', - 'serial_number': 'inbound_product:serial_number', - 'status': 'inbound_product:status', - 'quality_status': 'inbound_product:quality_status', - 'in_quantity': 'inbound_product:in_quantity', - 'stock_quantity': 'inbound_product:stock_quantity', - 'available_quantity': 'inbound_product:available_quantity', - 'warehouse_location': 'inbound_product:warehouse_location', - 'bom_code': 'inbound_product:bom_code', - 'bom_version': 'inbound_product:bom_version', - 'work_order_code': 'inbound_product:work_order_code', - 'order_id': 'inbound_product:order_id', - 'production_manager': 'inbound_product:production_manager', - 'production_start_time': 'inbound_product:production_start_time', - 'production_end_time': 'inbound_product:production_end_time', - 'raw_material_cost': 'inbound_product:raw_material_cost', - 'manual_cost': 'inbound_product:manual_cost', - 'sale_price': 'inbound_product:sale_price', - 'product_photo': 'inbound_product:product_photo', - 'quality_report_link': 'inbound_product:quality_report_link', - 'inspection_report_link': 'inbound_product:inspection_report_link', - 'detail_link': 'inbound_product:detail_link', - } - # 复制一份,避免遍历时修改字典 + field_to_perm = {'id': 'inbound_product:id', 'base_id': 'inbound_product:base_id', 'company_name': 'inbound_product:company_name', 'material_name': 'inbound_product:material_name', 'category': 'inbound_product:category', 'material_type': 'inbound_product:material_type', 'spec_model': 'inbound_product:spec_model', 'unit': 'inbound_product:unit', 'sku': 'inbound_product:sku', 'inbound_date': 'inbound_product:inbound_date', 'barcode': 'inbound_product:barcode', 'serial_number': 'inbound_product:serial_number', 'status': 'inbound_product:status', 'quality_status': 'inbound_product:quality_status', 'in_quantity': 'inbound_product:in_quantity', 'stock_quantity': 'inbound_product:stock_quantity', 'available_quantity': 'inbound_product:available_quantity', 'warehouse_location': 'inbound_product:warehouse_location', 'bom_code': 'inbound_product:bom_code', 'bom_version': 'inbound_product:bom_version', 'work_order_code': 'inbound_product:work_order_code', 'order_id': 'inbound_product:order_id', 'production_manager': 'inbound_product:production_manager', 'production_start_time': 'inbound_product:production_start_time', 'production_end_time': 'inbound_product:production_end_time', 'raw_material_cost': 'inbound_product:raw_material_cost', 'manual_cost': 'inbound_product:manual_cost', 'sale_price': 'inbound_product:sale_price', 'product_photo': 'inbound_product:product_photo', 'quality_report_link': 'inbound_product:quality_report_link', 'inspection_report_link': 'inbound_product:inspection_report_link', 'detail_link': 'inbound_product:detail_link'} 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) - + if perm_code and perm_code not in user_permissions: data.pop(field, None) ProductInboundService.update_inbound(id, data) return jsonify({"code": 200, "msg": "更新成功"}) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 4. 删除 -# ------------------------------------------------------------------ @inbound_product_bp.route('/', methods=['DELETE']) @permission_required('inbound_product:operation') def delete(id): @@ -302,10 +132,6 @@ def delete(id): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 5. 获取出库历史 -# ------------------------------------------------------------------ @inbound_product_bp.route('//history', methods=['GET']) @permission_required('inbound_product') def get_history(id): @@ -316,10 +142,6 @@ def get_history(id): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 6. 系统用户建议 -# ------------------------------------------------------------------ @inbound_product_bp.route('/suggestions/users', methods=['GET']) @permission_required('inbound_product') def get_user_suggestions(): @@ -327,10 +149,6 @@ def get_user_suggestions(): data = ProductInboundService.search_system_users(keyword) return jsonify({"code": 200, "msg": "success", "data": data}) - -# ------------------------------------------------------------------ -# 7. 获取筛选选项 -# ------------------------------------------------------------------ @inbound_product_bp.route('/options', methods=['GET']) @permission_required('inbound_product') def get_options(): @@ -340,18 +158,12 @@ def get_options(): except Exception as e: return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 8. 获取历史生产负责人建议 (新增) -# ------------------------------------------------------------------ @inbound_product_bp.route('/suggestions/managers', methods=['GET']) @permission_required('inbound_product') def get_manager_history(): - base_id = request.args.get('base_id', type=int) - if not base_id: - return jsonify({"code": 400, "msg": "缺少 base_id"}), 400 + keyword = request.args.get('keyword', '') try: - data = ProductInboundService.get_history_managers(base_id) + data = ProductInboundService.get_history_managers(keyword) return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: traceback.print_exc() diff --git a/inventory-backend/app/api/v1/inbound/semi.py b/inventory-backend/app/api/v1/inbound/semi.py index cb20d12..c121ba4 100644 --- a/inventory-backend/app/api/v1/inbound/semi.py +++ b/inventory-backend/app/api/v1/inbound/semi.py @@ -4,151 +4,49 @@ from app.services.inbound.semi_service import SemiInboundService from app.utils.decorators import permission_required import traceback -# 定义蓝图 +# === 这一行非常关键,绝对不能丢!=== inbound_semi_bp = Blueprint('stock_semi', __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_semi: 开头的权限码(这里我们返回一个特殊标记,表示全部) - # 为了简单,我们返回 ['inbound_semi:*'],在过滤函数中特殊处理 - return ['inbound_semi:*'] + if not user_role: return [] + if user_role.upper() == 'SUPER_ADMIN': return ['inbound_semi:*'] perm_dict = AuthService.get_user_permissions(user_role) - # 合并菜单和元素权限 - perms = perm_dict.get('menus', []) + perm_dict.get('elements', []) - return perms - + return perm_dict.get('menus', []) + perm_dict.get('elements', []) def filter_item_by_permissions(item_dict, user_permissions): - """ - 根据用户权限过滤 item 字典,无权限的字段值置为 None - """ - # 字段名到权限码的映射(与前端 permissionMap 保持一致) field_to_perm = { - 'id': 'inbound_semi:id', - 'base_id': 'inbound_semi:base_id', - 'company_name': 'inbound_semi:company_name', - 'material_name': 'inbound_semi:material_name', - 'category': 'inbound_semi:category', - 'material_type': 'inbound_semi:material_type', - 'spec_model': 'inbound_semi:spec_model', - 'unit': 'inbound_semi:unit', - 'sku': 'inbound_semi:sku', - 'inbound_date': 'inbound_semi:inbound_date', - 'barcode': 'inbound_semi:barcode', - 'serial_number': 'inbound_semi:serial_number', - 'batch_number': 'inbound_semi:batch_number', - 'status': 'inbound_semi:status', - 'quality_status': 'inbound_semi:quality_status', - 'in_quantity': 'inbound_semi:in_quantity', - 'stock_quantity': 'inbound_semi:stock_quantity', - 'available_quantity': 'inbound_semi:available_quantity', - 'warehouse_location': 'inbound_semi:warehouse_location', - 'bom_code': 'inbound_semi:bom_code', - 'bom_version': 'inbound_semi:bom_version', - 'work_order_code': 'inbound_semi:work_order_code', - 'raw_material_cost': 'inbound_semi:raw_material_cost', - 'manual_cost': 'inbound_semi:manual_cost', - 'unit_total_cost': 'inbound_semi:unit_total_cost', - 'production_manager': 'inbound_semi:production_manager', - 'production_start_time': 'inbound_semi:production_start_time', - 'production_end_time': 'inbound_semi:production_end_time', - 'arrival_photo': 'inbound_semi:arrival_photo', - 'quality_report_link': 'inbound_semi:quality_report_link', + 'id': 'inbound_semi:id', 'base_id': 'inbound_semi:base_id', 'company_name': 'inbound_semi:company_name', + 'material_name': 'inbound_semi:material_name', 'category': 'inbound_semi:category', + 'material_type': 'inbound_semi:material_type', 'spec_model': 'inbound_semi:spec_model', + 'unit': 'inbound_semi:unit', 'sku': 'inbound_semi:sku', 'inbound_date': 'inbound_semi:inbound_date', + 'barcode': 'inbound_semi:barcode', 'serial_number': 'inbound_semi:serial_number', + 'batch_number': 'inbound_semi:batch_number', 'status': 'inbound_semi:status', + 'quality_status': 'inbound_semi:quality_status', 'in_quantity': 'inbound_semi:in_quantity', + 'stock_quantity': 'inbound_semi:stock_quantity', 'available_quantity': 'inbound_semi:available_quantity', + 'warehouse_location': 'inbound_semi:warehouse_location', 'bom_code': 'inbound_semi:bom_code', + 'bom_version': 'inbound_semi:bom_version', 'work_order_code': 'inbound_semi:work_order_code', + 'raw_material_cost': 'inbound_semi:raw_material_cost', 'manual_cost': 'inbound_semi:manual_cost', + 'unit_total_cost': 'inbound_semi:unit_total_cost', 'production_manager': 'inbound_semi:production_manager', + 'production_start_time': 'inbound_semi:production_start_time', 'production_end_time': 'inbound_semi:production_end_time', + 'arrival_photo': 'inbound_semi:arrival_photo', 'quality_report_link': 'inbound_semi:quality_report_link', 'detail_link': 'inbound_semi:detail_link', } - # 如果用户是超级管理员且有 'inbound_semi:*',则不过滤 - if 'inbound_semi:*' in user_permissions: - return item_dict + if 'inbound_semi:*' 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 + if field in item_dict and perm_code not in user_permissions: item_dict[field] = None return item_dict - -# ------------------------------------------------------------------ -# 0. 基础物料搜索 (复用逻辑) -# ------------------------------------------------------------------ @inbound_semi_bp.route('/search-base', methods=['GET']) @permission_required('inbound_semi') def search_base(): - """ - 供前端下拉框远程搜索使用 (搜索半成品类型的基础物料) - Query Param: keyword (名称或规格) - """ try: keyword = request.args.get('keyword', '') page = request.args.get('page', 1, type=int) - # 这里复用 Service 中的搜索逻辑 (已修改接收 page) result = SemiInboundService.search_base_material(keyword, page) - # 字段级脱敏 - 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 - -# ------------------------------------------------------------------ -# 0.5 [新增] BOM 搜索接口 -# ------------------------------------------------------------------ -@inbound_semi_bp.route('/search-bom', methods=['GET']) -@permission_required('inbound_semi') -def search_bom(): - """ - 供前端下拉框远程搜索使用 (搜索BOM) - Query Param: keyword (编号或父件规格) - """ - try: - keyword = request.args.get('keyword', '') - data = SemiInboundService.search_bom_options(keyword) - return jsonify({ - "code": 200, - "msg": "success", - "data": data - }) - except Exception as e: - traceback.print_exc() - return jsonify({"code": 500, "msg": str(e)}), 500 - - -# ------------------------------------------------------------------ -# 1. 获取半成品列表 -# ------------------------------------------------------------------ -@inbound_semi_bp.route('/list', methods=['GET']) -@permission_required('inbound_semi') -def get_list(): - try: - page = request.args.get('page', 1, type=int) - limit = request.args.get('pageSize', 15, type=int) - # 支持按关键字搜索:BOM号、工单号、SN、批号等 - keyword = request.args.get('keyword', '') - - # [修改] 获取状态列表参数 - statuses_str = request.args.get('statuses', '') - statuses = statuses_str.split(',') if statuses_str else [] - - result = SemiInboundService.get_list(page, limit, keyword, statuses) - # 字段级脱敏 user_permissions = get_current_user_permissions() if result.get('items'): result['items'] = [filter_item_by_permissions(item, user_permissions) for item in result['items']] @@ -157,137 +55,70 @@ def get_list(): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 +@inbound_semi_bp.route('/search-bom', methods=['GET']) +@permission_required('inbound_semi') +def search_bom(): + try: + keyword = request.args.get('keyword', '') + data = SemiInboundService.search_bom_options(keyword) + return jsonify({"code": 200, "msg": "success", "data": data}) + except Exception as e: + traceback.print_exc() + return jsonify({"code": 500, "msg": str(e)}), 500 + +@inbound_semi_bp.route('/list', methods=['GET']) +@permission_required('inbound_semi') +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', '') + statuses_str = request.args.get('statuses', '') + statuses = statuses_str.split(',') if statuses_str else [] + result = SemiInboundService.get_list(page, limit, keyword, statuses) + 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_semi_bp.route('/submit', methods=['POST']) @permission_required('inbound_semi:operation') def submit(): try: data = request.get_json() - if not data: - return jsonify({"code": 400, "msg": "No data"}), 400 - - # 数据清洗:移除用户没有权限的字段 + if not data: return jsonify({"code": 400, "msg": "No data"}), 400 user_permissions = get_current_user_permissions() - # 超级管理员不过滤 if 'inbound_semi:*' not in user_permissions: - # 字段名到权限码的映射(与前端 permissionMap 保持一致) - field_to_perm = { - 'id': 'inbound_semi:id', - 'base_id': 'inbound_semi:base_id', - 'company_name': 'inbound_semi:company_name', - 'material_name': 'inbound_semi:material_name', - 'category': 'inbound_semi:category', - 'material_type': 'inbound_semi:material_type', - 'spec_model': 'inbound_semi:spec_model', - 'unit': 'inbound_semi:unit', - 'sku': 'inbound_semi:sku', - 'inbound_date': 'inbound_semi:inbound_date', - 'barcode': 'inbound_semi:barcode', - 'serial_number': 'inbound_semi:serial_number', - 'batch_number': 'inbound_semi:batch_number', - 'status': 'inbound_semi:status', - 'quality_status': 'inbound_semi:quality_status', - 'in_quantity': 'inbound_semi:in_quantity', - 'stock_quantity': 'inbound_semi:stock_quantity', - 'available_quantity': 'inbound_semi:available_quantity', - 'warehouse_location': 'inbound_semi:warehouse_location', - 'bom_code': 'inbound_semi:bom_code', - 'bom_version': 'inbound_semi:bom_version', - 'work_order_code': 'inbound_semi:work_order_code', - 'raw_material_cost': 'inbound_semi:raw_material_cost', - 'manual_cost': 'inbound_semi:manual_cost', - 'unit_total_cost': 'inbound_semi:unit_total_cost', - 'production_manager': 'inbound_semi:production_manager', - 'production_start_time': 'inbound_semi:production_start_time', - 'production_end_time': 'inbound_semi:production_end_time', - 'arrival_photo': 'inbound_semi:arrival_photo', - 'quality_report_link': 'inbound_semi:quality_report_link', - 'detail_link': 'inbound_semi:detail_link', - } - # 复制一份,避免遍历时修改字典 + field_to_perm = {'id': 'inbound_semi:id', 'base_id': 'inbound_semi:base_id', 'company_name': 'inbound_semi:company_name', 'material_name': 'inbound_semi:material_name', 'category': 'inbound_semi:category', 'material_type': 'inbound_semi:material_type', 'spec_model': 'inbound_semi:spec_model', 'unit': 'inbound_semi:unit', 'sku': 'inbound_semi:sku', 'inbound_date': 'inbound_semi:inbound_date', 'barcode': 'inbound_semi:barcode', 'serial_number': 'inbound_semi:serial_number', 'batch_number': 'inbound_semi:batch_number', 'status': 'inbound_semi:status', 'quality_status': 'inbound_semi:quality_status', 'in_quantity': 'inbound_semi:in_quantity', 'stock_quantity': 'inbound_semi:stock_quantity', 'available_quantity': 'inbound_semi:available_quantity', 'warehouse_location': 'inbound_semi:warehouse_location', 'bom_code': 'inbound_semi:bom_code', 'bom_version': 'inbound_semi:bom_version', 'work_order_code': 'inbound_semi:work_order_code', 'raw_material_cost': 'inbound_semi:raw_material_cost', 'manual_cost': 'inbound_semi:manual_cost', 'unit_total_cost': 'inbound_semi:unit_total_cost', 'production_manager': 'inbound_semi:production_manager', 'production_start_time': 'inbound_semi:production_start_time', 'production_end_time': 'inbound_semi:production_end_time', 'arrival_photo': 'inbound_semi:arrival_photo', 'quality_report_link': 'inbound_semi:quality_report_link', 'detail_link': 'inbound_semi:detail_link'} 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) - - # 修改:调用 Service 处理入库,获取新创建的对象 + if perm_code and perm_code not in user_permissions: data.pop(field, None) new_stock = SemiInboundService.handle_inbound(data) - - # 修改:返回成功信息以及新创建的数据(包含生成的ID和SKU),供前端打印使用 - return jsonify({ - "code": 200, - "msg": "入库成功", - "data": new_stock.to_dict() - }) + 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_semi_bp.route('/', methods=['PUT']) @permission_required('inbound_semi:operation') def update_semi(id): try: data = request.get_json() - # 数据清洗:移除用户没有权限的字段 user_permissions = get_current_user_permissions() - # 超级管理员不过滤 if 'inbound_semi:*' not in user_permissions: - field_to_perm = { - 'id': 'inbound_semi:id', - 'base_id': 'inbound_semi:base_id', - 'company_name': 'inbound_semi:company_name', - 'material_name': 'inbound_semi:material_name', - 'category': 'inbound_semi:category', - 'material_type': 'inbound_semi:material_type', - 'spec_model': 'inbound_semi:spec_model', - 'unit': 'inbound_semi:unit', - 'sku': 'inbound_semi:sku', - 'inbound_date': 'inbound_semi:inbound_date', - 'barcode': 'inbound_semi:barcode', - 'serial_number': 'inbound_semi:serial_number', - 'batch_number': 'inbound_semi:batch_number', - 'status': 'inbound_semi:status', - 'quality_status': 'inbound_semi:quality_status', - 'in_quantity': 'inbound_semi:in_quantity', - 'stock_quantity': 'inbound_semi:stock_quantity', - 'available_quantity': 'inbound_semi:available_quantity', - 'warehouse_location': 'inbound_semi:warehouse_location', - 'bom_code': 'inbound_semi:bom_code', - 'bom_version': 'inbound_semi:bom_version', - 'work_order_code': 'inbound_semi:work_order_code', - 'raw_material_cost': 'inbound_semi:raw_material_cost', - 'manual_cost': 'inbound_semi:manual_cost', - 'unit_total_cost': 'inbound_semi:unit_total_cost', - 'production_manager': 'inbound_semi:production_manager', - 'production_start_time': 'inbound_semi:production_start_time', - 'production_end_time': 'inbound_semi:production_end_time', - 'arrival_photo': 'inbound_semi:arrival_photo', - 'quality_report_link': 'inbound_semi:quality_report_link', - 'detail_link': 'inbound_semi:detail_link', - } - # 复制一份,避免遍历时修改字典 + field_to_perm = {'id': 'inbound_semi:id', 'base_id': 'inbound_semi:base_id', 'company_name': 'inbound_semi:company_name', 'material_name': 'inbound_semi:material_name', 'category': 'inbound_semi:category', 'material_type': 'inbound_semi:material_type', 'spec_model': 'inbound_semi:spec_model', 'unit': 'inbound_semi:unit', 'sku': 'inbound_semi:sku', 'inbound_date': 'inbound_semi:inbound_date', 'barcode': 'inbound_semi:barcode', 'serial_number': 'inbound_semi:serial_number', 'batch_number': 'inbound_semi:batch_number', 'status': 'inbound_semi:status', 'quality_status': 'inbound_semi:quality_status', 'in_quantity': 'inbound_semi:in_quantity', 'stock_quantity': 'inbound_semi:stock_quantity', 'available_quantity': 'inbound_semi:available_quantity', 'warehouse_location': 'inbound_semi:warehouse_location', 'bom_code': 'inbound_semi:bom_code', 'bom_version': 'inbound_semi:bom_version', 'work_order_code': 'inbound_semi:work_order_code', 'raw_material_cost': 'inbound_semi:raw_material_cost', 'manual_cost': 'inbound_semi:manual_cost', 'unit_total_cost': 'inbound_semi:unit_total_cost', 'production_manager': 'inbound_semi:production_manager', 'production_start_time': 'inbound_semi:production_start_time', 'production_end_time': 'inbound_semi:production_end_time', 'arrival_photo': 'inbound_semi:arrival_photo', 'quality_report_link': 'inbound_semi:quality_report_link', 'detail_link': 'inbound_semi:detail_link'} 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) - + if perm_code and perm_code not in user_permissions: data.pop(field, None) SemiInboundService.update_inbound(id, data) return jsonify({"code": 200, "msg": "更新成功"}) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 4. 删除半成品入库记录 -# ------------------------------------------------------------------ @inbound_semi_bp.route('/', methods=['DELETE']) @permission_required('inbound_semi:operation') def delete_semi(id): @@ -298,28 +129,16 @@ def delete_semi(id): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 5. [新增] 获取关联出库历史 -# ------------------------------------------------------------------ @inbound_semi_bp.route('//history', methods=['GET']) @permission_required('inbound_semi') def get_history(id): try: data = SemiInboundService.get_outbound_history(id) - return jsonify({ - "code": 200, - "msg": "success", - "data": data - }) + return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 - -# ------------------------------------------------------------------ -# 6. 系统用户建议 -# ------------------------------------------------------------------ @inbound_semi_bp.route('/suggestions/users', methods=['GET']) @permission_required('inbound_semi') def get_user_suggestions(): @@ -327,10 +146,6 @@ def get_user_suggestions(): data = SemiInboundService.search_system_users(keyword) return jsonify({"code": 200, "msg": "success", "data": data}) - -# ------------------------------------------------------------------ -# 7. 获取筛选选项 -# ------------------------------------------------------------------ @inbound_semi_bp.route('/options', methods=['GET']) @permission_required('inbound_semi') def get_options(): @@ -340,17 +155,12 @@ def get_options(): except Exception as e: return jsonify({"code": 500, "msg": str(e)}), 500 -# ------------------------------------------------------------------ -# 8. 获取历史生产负责人建议 (新增) -# ------------------------------------------------------------------ @inbound_semi_bp.route('/suggestions/managers', methods=['GET']) @permission_required('inbound_semi') def get_manager_history(): - base_id = request.args.get('base_id', type=int) - if not base_id: - return jsonify({"code": 400, "msg": "缺少 base_id"}), 400 + keyword = request.args.get('keyword', '') try: - data = SemiInboundService.get_history_managers(base_id) + data = SemiInboundService.get_history_managers(keyword) return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: traceback.print_exc() diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py index 0b285d8..d2284ff 100644 --- a/inventory-backend/app/services/inbound/product_service.py +++ b/inventory-backend/app/services/inbound/product_service.py @@ -399,17 +399,19 @@ class ProductInboundService: return {"categories": [], "types": [], "companies": []} # ============================================================ - # 8. 获取历史负责人建议 (新增) + # 8. 获取历史负责人建议 (修改为全局查询) # ============================================================ @staticmethod - def get_history_managers(base_id): + def get_history_managers(keyword=None): from app.models.inbound.product import StockProduct try: - records = db.session.query(StockProduct.production_manager).filter( - StockProduct.base_id == base_id, + query = db.session.query(StockProduct.production_manager).filter( StockProduct.production_manager.isnot(None), StockProduct.production_manager != '' - ).distinct().all() + ) + if keyword: + query = query.filter(StockProduct.production_manager.ilike(f'%{keyword}%')) + records = query.distinct().all() return [r[0] for r in records if r[0]] except Exception: traceback.print_exc() diff --git a/inventory-backend/app/services/inbound/semi_service.py b/inventory-backend/app/services/inbound/semi_service.py index f370f59..95099fb 100644 --- a/inventory-backend/app/services/inbound/semi_service.py +++ b/inventory-backend/app/services/inbound/semi_service.py @@ -487,17 +487,19 @@ class SemiInboundService: return {"categories": [], "types": [], "companies": []} # ============================================================ - # 8. 获取历史生产负责人 (新增) + # 8. 获取历史生产负责人 (修改为全局查询) # ============================================================ @staticmethod - def get_history_managers(base_id): + def get_history_managers(keyword=None): from app.models.inbound.semi import StockSemi try: - records = db.session.query(StockSemi.production_manager).filter( - StockSemi.base_id == base_id, + query = db.session.query(StockSemi.production_manager).filter( StockSemi.production_manager.isnot(None), StockSemi.production_manager != '' - ).distinct().all() + ) + if keyword: + query = query.filter(StockSemi.production_manager.ilike(f'%{keyword}%')) + records = query.distinct().all() return [r[0] for r in records if r[0]] except Exception: traceback.print_exc() diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue index 6090ecc..3ce5da1 100644 --- a/inventory-web/src/views/stock/inbound/product.vue +++ b/inventory-web/src/views/stock/inbound/product.vue @@ -108,8 +108,7 @@