From 853374de5d35ffa05c21540f5228d69842de9350 Mon Sep 17 00:00:00 2001 From: dxc Date: Thu, 12 Feb 2026 17:16:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E5=8D=8A=E6=88=90=E5=93=81=E5=92=8C?= =?UTF-8?q?=E6=88=90=E5=93=81=E8=B7=9Fbom=E8=A1=A8=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/api/v1/inbound/product.py | 22 ++- inventory-backend/app/api/v1/inbound/semi.py | 23 +++- .../app/services/inbound/product_service.py | 114 +++++++-------- .../app/services/inbound/semi_service.py | 130 +++++++----------- inventory-web/src/api/inbound/product.ts | 11 +- inventory-web/src/api/inbound/semi.ts | 11 +- .../src/views/stock/inbound/product.vue | 97 +++++++++---- .../src/views/stock/inbound/semi.vue | 76 ++++++++-- 8 files changed, 292 insertions(+), 192 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/product.py b/inventory-backend/app/api/v1/inbound/product.py index c4c2aac..9fca920 100644 --- a/inventory-backend/app/api/v1/inbound/product.py +++ b/inventory-backend/app/api/v1/inbound/product.py @@ -25,6 +25,26 @@ def search_base(): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 +# ------------------------------------------------------------------ +# 0.5 [新增] BOM 搜索接口 +# ------------------------------------------------------------------ +@inbound_product_bp.route('/search-bom', methods=['GET']) +def search_bom(): + """ + 供前端下拉框远程搜索使用 (搜索BOM) + """ + try: + keyword = request.args.get('keyword', '') + data = ProductInboundService.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. 获取列表 (支持 status 多选筛选) @@ -125,4 +145,4 @@ def get_options(): data = ProductInboundService.get_filter_options() return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: - return jsonify({"code": 500, "msg": str(e)}), 500 + return jsonify({"code": 500, "msg": str(e)}), 500 \ No newline at end of file diff --git a/inventory-backend/app/api/v1/inbound/semi.py b/inventory-backend/app/api/v1/inbound/semi.py index c2b4c4f..39d2388 100644 --- a/inventory-backend/app/api/v1/inbound/semi.py +++ b/inventory-backend/app/api/v1/inbound/semi.py @@ -29,6 +29,27 @@ def search_base(): traceback.print_exc() return jsonify({"code": 500, "msg": str(e)}), 500 +# ------------------------------------------------------------------ +# 0.5 [新增] BOM 搜索接口 +# ------------------------------------------------------------------ +@inbound_semi_bp.route('/search-bom', methods=['GET']) +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. 获取半成品列表 @@ -139,4 +160,4 @@ def get_options(): data = SemiInboundService.get_filter_options() return jsonify({"code": 200, "msg": "success", "data": data}) except Exception as e: - return jsonify({"code": 500, "msg": str(e)}), 500 + return jsonify({"code": 500, "msg": str(e)}), 500 \ No newline at end of file diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py index da06ce3..219f7d0 100644 --- a/inventory-backend/app/services/inbound/product_service.py +++ b/inventory-backend/app/services/inbound/product_service.py @@ -11,26 +11,17 @@ import json class ProductInboundService: # ============================================================ - # 0. 辅助:唯一性校验 (新增核心逻辑) + # 0. 辅助:唯一性校验 # ============================================================ @staticmethod def _check_unique(serial_number, exclude_id=None): - """ - 校验成品的唯一性 - :param serial_number: 序列号 - :param exclude_id: 排除的ID (编辑模式用) - """ from app.models.inbound.product import StockProduct - - # 成品强校验序列号 (SN) - SN应该是全局唯一的 if serial_number: query = StockProduct.query.filter(StockProduct.serial_number == serial_number) if exclude_id: query = query.filter(StockProduct.id != exclude_id) - exists = query.first() if exists: - # [修改] material -> base occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料" raise ValueError(f"序列号【{serial_number}】已存在!被成品 [{occupied_name}] 占用,请核查。") @@ -40,10 +31,7 @@ class ProductInboundService: @staticmethod def search_base_material(keyword): try: - # [核心修改] 只查询已启用的物料 query = MaterialBase.query.filter(MaterialBase.is_enabled == True) - - # 2. 动态条件:如果传入了关键词,则增加模糊匹配条件 if keyword: query = query.filter( or_( @@ -51,11 +39,7 @@ class ProductInboundService: MaterialBase.spec_model.ilike(f'%{keyword}%') ) ) - - # 3. 排序与限制:按ID倒序,取最新20条 query = query.order_by(MaterialBase.id.desc()).limit(20) - - # 4. 结果封装 results = [] for item in query.all(): results.append({ @@ -73,33 +57,69 @@ class ProductInboundService: return [] # ============================================================ - # 2. 新增入库逻辑 (强制北京时间 + 唯一性校验) + # 1.5 [新增] BOM 搜索逻辑 + # ============================================================ + @staticmethod + def search_bom_options(keyword): + from app.models.bom import BomTable + try: + # 关联查询:BOM表 + 父件基础信息表 + query = db.session.query( + BomTable.bom_no, + BomTable.version, + MaterialBase.name.label('parent_name'), + MaterialBase.spec_model.label('parent_spec') + ).join(MaterialBase, BomTable.parent_id == MaterialBase.id) + + # 只查询启用的BOM + if hasattr(BomTable, 'is_enabled'): + query = query.filter(BomTable.is_enabled == True) + + if keyword: + kw = f'%{keyword}%' + # 支持搜索:BOM编号、父件名称、父件规格 + query = query.filter( + or_( + BomTable.bom_no.ilike(kw), + MaterialBase.name.ilike(kw), + MaterialBase.spec_model.ilike(kw) + ) + ) + + # 去重并限制数量 + results = query.distinct().limit(20).all() + + return [{ + 'bom_no': r.bom_no, + 'version': r.version, + 'parent_name': r.parent_name, + 'parent_spec': r.parent_spec or '' + } for r in results] + except Exception: + traceback.print_exc() + return [] + + # ============================================================ + # 2. 新增入库逻辑 # ============================================================ @staticmethod def handle_inbound(data): from app.models.inbound.product import StockProduct - try: base_id = data.get('base_id') if not base_id: raise ValueError("必须选择基础物料") material = MaterialBase.query.get(base_id) if not material: raise ValueError("物料不存在") - - # [核心修改] 后端二次校验:如果物料已停用,禁止入库 if not material.is_enabled: raise ValueError(f"物料【{material.name}】已停用,无法办理新入库。") - # --- [核心修改] 执行唯一性校验 --- ProductInboundService._check_unique( serial_number=data.get('serial_number') ) - # [核心修改] 强制北京时间 beijing_tz = timezone(timedelta(hours=8)) current_time = datetime.now(beijing_tz).replace(tzinfo=None) - in_date_val = current_time - if data.get('in_date'): try: date_str = str(data['in_date']) @@ -113,12 +133,10 @@ class ProductInboundService: in_date_val = current_time in_qty = float(data.get('in_quantity') or 0) - p_start = data.get('production_start_time', '') p_end = data.get('production_end_time', '') time_range = f"{p_start} ~ {p_end}" if p_start or p_end else None - # 全局流水号 try: seq_sql = text("SELECT nextval('global_print_seq')") result = db.session.execute(seq_sql) @@ -132,7 +150,6 @@ class ProductInboundService: photo_list = data.get('product_photo', []) quality_list = data.get('quality_report_link', []) inspection_list = data.get('inspection_report_link', []) - if not isinstance(photo_list, list): photo_list = [] if not isinstance(quality_list, list): quality_list = [] if not isinstance(inspection_list, list): inspection_list = [] @@ -141,39 +158,30 @@ class ProductInboundService: base_id=material.id, global_print_id=next_global_id, sku=generated_sku, - production_date=in_date_val, # 存入 DateTime + production_date=in_date_val, barcode=final_barcode, serial_number=data.get('serial_number'), - status=data.get('status', '在库'), warehouse_location=data.get('warehouse_location'), - in_quantity=in_qty, stock_quantity=in_qty, available_quantity=in_qty, - bom_code=data.get('bom_code'), bom_version=data.get('bom_version'), work_order_code=data.get('work_order_code'), production_manager=data.get('production_manager'), production_time_range=time_range, - raw_material_cost=float(data.get('raw_material_cost') or 0), manual_cost=float(data.get('manual_cost') or 0), - quality_status=data.get('quality_status', '合格'), - product_photo=json.dumps(photo_list), quality_report_link=json.dumps(quality_list), inspection_report_link=json.dumps(inspection_list), - detail_link=data.get('detail_link'), remark=data.get('remark'), - sale_price=float(data.get('sale_price') or 0), order_id=data.get('order_id') ) - db.session.add(new_stock) db.session.commit() return new_stock @@ -187,12 +195,10 @@ class ProductInboundService: @staticmethod def update_inbound(stock_id, data): from app.models.inbound.product import StockProduct - try: stock = StockProduct.query.get(stock_id) if not stock: raise ValueError("记录不存在") - # --- [核心修改] 编辑时也要校验唯一性 --- if 'serial_number' in data: ProductInboundService._check_unique( serial_number=data['serial_number'], @@ -211,11 +217,9 @@ class ProductInboundService: if 'product_photo' in data: imgs = data['product_photo'] if isinstance(imgs, list): stock.product_photo = json.dumps(imgs) - if 'quality_report_link' in data: imgs = data['quality_report_link'] if isinstance(imgs, list): stock.quality_report_link = json.dumps(imgs) - if 'inspection_report_link' in data: imgs = data['inspection_report_link'] if isinstance(imgs, list): stock.inspection_report_link = json.dumps(imgs) @@ -267,7 +271,6 @@ class ProductInboundService: # ============================================================ @staticmethod def get_outbound_history(stock_id): - """获取出库历史""" try: records = TransOutbound.query.filter_by( source_table='stock_product', stock_id=stock_id @@ -284,7 +287,6 @@ class ProductInboundService: from app.models.inbound.product import StockProduct try: query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id) - if keyword: query = query.filter(or_( MaterialBase.name.ilike(f'%{keyword}%'), @@ -294,17 +296,12 @@ class ProductInboundService: StockProduct.order_id.ilike(f'%{keyword}%'), StockProduct.sku.ilike(f'%{keyword}%') )) - - # 类别筛选 if category and category.strip(): query = query.filter(MaterialBase.category == category.strip()) - # 类型筛选 if material_type and material_type.strip(): query = query.filter(MaterialBase.material_type == material_type.strip()) - if not statuses: statuses = ['在库', '借库'] - if '已出库' in statuses: query = query.filter(StockProduct.status.in_(statuses)) else: @@ -314,11 +311,8 @@ class ProductInboundService: StockProduct.stock_quantity > 0 ) ) - - # 按照 production_date (入库日期) 倒序排序 pagination = query.order_by(StockProduct.production_date.desc()).paginate(page=page, per_page=limit, error_out=False) - current_items = pagination.items def parse_img(json_str): @@ -330,10 +324,7 @@ class ProductInboundService: items = [] for item in current_items: - # [注意] 因为Model层已经修改了 to_dict 内部的 material -> base,所以这里直接调 to_dict 即可 d = item.to_dict() - - # 格式化日期 date_display = '' if item.production_date: try: @@ -341,30 +332,22 @@ class ProductInboundService: except: date_display = str(item.production_date)[:10] d['inbound_date'] = date_display - d['qty_stock'] = float(item.stock_quantity or 0) d['qty_available'] = float(item.available_quantity or 0) d['sum_stock'] = d['qty_stock'] d['sum_available'] = d['qty_available'] - d['product_photo'] = parse_img(item.product_photo) d['quality_report_link'] = parse_img(item.quality_report_link) d['inspection_report_link'] = parse_img(item.inspection_report_link) d['global_print_id'] = item.global_print_id - items.append(d) - return {"total": pagination.total, "items": items} except: traceback.print_exc() return {"total": 0, "items": []} - # ============================================================ - # 7. 系统用户搜索 - # ============================================================ @staticmethod def search_system_users(keyword): - """搜索系统用户(活跃状态)""" from app.models.system import SysUser try: query = SysUser.query.filter(SysUser.status == 'active') @@ -385,9 +368,6 @@ class ProductInboundService: except Exception: return [] - # ============================================================ - # 8. 获取筛选选项(类别、类型) - # ============================================================ @staticmethod def get_filter_options(): try: @@ -405,4 +385,4 @@ class ProductInboundService: except Exception: import traceback traceback.print_exc() - return {"categories": [], "types": []} + return {"categories": [], "types": []} \ No newline at end of file diff --git a/inventory-backend/app/services/inbound/semi_service.py b/inventory-backend/app/services/inbound/semi_service.py index f9a770c..89403a6 100644 --- a/inventory-backend/app/services/inbound/semi_service.py +++ b/inventory-backend/app/services/inbound/semi_service.py @@ -11,32 +11,20 @@ import json class SemiInboundService: # ============================================================ - # 0. 辅助:唯一性校验 (新增核心逻辑) + # 0. 辅助:唯一性校验 # ============================================================ @staticmethod def _check_unique(base_id, serial_number, batch_number, exclude_id=None): - """ - 校验半成品的唯一性 - :param base_id: 基础物料ID - :param serial_number: 序列号 - :param batch_number: 批号 - :param exclude_id: 排除的ID - """ from app.models.inbound.semi import StockSemi - - # 1. 序列号 (SN) 校验 - 全局唯一 if serial_number: query = StockSemi.query.filter(StockSemi.serial_number == serial_number) if exclude_id: query = query.filter(StockSemi.id != exclude_id) - exists = query.first() if exists: - # [修改] material -> base occupied_name = exists.base.name if (hasattr(exists, 'base') and exists.base) else "未知物料" raise ValueError(f"序列号【{serial_number}】已存在!被半成品 [{occupied_name}] 占用,请核查。") - # 2. 批号 (BN) 校验 - 同物料下不能重复开单 if batch_number and base_id: query = StockSemi.query.filter( StockSemi.base_id == base_id, @@ -44,7 +32,6 @@ class SemiInboundService: ) if exclude_id: query = query.filter(StockSemi.id != exclude_id) - if query.first(): raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复建单,建议在原批次上追加库存。") @@ -54,10 +41,7 @@ class SemiInboundService: @staticmethod def search_base_material(keyword): try: - # [核心修改] 只查询已启用的物料 query = MaterialBase.query.filter(MaterialBase.is_enabled == True) - - # 如果有关键词,进行模糊匹配 if keyword: query = query.filter( or_( @@ -65,19 +49,16 @@ class SemiInboundService: MaterialBase.spec_model.ilike(f'%{keyword}%') ) ) - - # 统一逻辑:按ID倒序,限制20条 query = query.order_by(MaterialBase.id.desc()).limit(20) - results = [] for item in query.all(): results.append({ 'id': item.id, 'name': item.name, - 'spec': item.spec_model, # 对应前端 item.spec + 'spec': item.spec_model, 'category': item.category, 'unit': item.unit, - 'type': item.material_type, # 对应前端 item.type + 'type': item.material_type, 'status': '启用' }) return results @@ -85,39 +66,74 @@ class SemiInboundService: traceback.print_exc() return [] + # ============================================================ + # 1.5 [新增] BOM 搜索逻辑 + # ============================================================ + @staticmethod + def search_bom_options(keyword): + from app.models.bom import BomTable + try: + # 关联查询:BOM表 + 父件基础信息表 + query = db.session.query( + BomTable.bom_no, + BomTable.version, + MaterialBase.name.label('parent_name'), + MaterialBase.spec_model.label('parent_spec') + ).join(MaterialBase, BomTable.parent_id == MaterialBase.id) + + # 只查询启用的BOM + if hasattr(BomTable, 'is_enabled'): + query = query.filter(BomTable.is_enabled == True) + + if keyword: + kw = f'%{keyword}%' + # 支持搜索:BOM编号、父件名称、父件规格 + query = query.filter( + or_( + BomTable.bom_no.ilike(kw), + MaterialBase.name.ilike(kw), + MaterialBase.spec_model.ilike(kw) + ) + ) + + # 去重并限制数量 + results = query.distinct().limit(20).all() + + return [{ + 'bom_no': r.bom_no, + 'version': r.version, + 'parent_name': r.parent_name, + 'parent_spec': r.parent_spec or '' + } for r in results] + except Exception: + traceback.print_exc() + return [] + # ============================================================ # 2. 新增入库逻辑 # ============================================================ @staticmethod def handle_inbound(data): from app.models.inbound.semi import StockSemi - try: base_id = data.get('base_id') if not base_id: raise ValueError("必须选择基础物料 (缺少 base_id)") - material = MaterialBase.query.get(base_id) if not material: raise ValueError(f"ID为 {base_id} 的基础物料不存在") - - # [核心修改] 后端二次校验:如果物料已停用,禁止入库 if not material.is_enabled: raise ValueError(f"物料【{material.name}】已停用,无法办理新入库。") - # --- [核心修改] 执行唯一性校验 --- SemiInboundService._check_unique( base_id=base_id, serial_number=data.get('serial_number'), batch_number=data.get('batch_number') ) - # [核心修改] 强制北京时间 beijing_tz = timezone(timedelta(hours=8)) current_time = datetime.now(beijing_tz).replace(tzinfo=None) - in_date_val = current_time - if data.get('in_date'): try: date_str = str(data['in_date']) @@ -130,7 +146,6 @@ class SemiInboundService: except ValueError: in_date_val = current_time - # 2. 处理生产时间 p_start = None p_end = None if data.get('production_start_time'): @@ -151,14 +166,12 @@ class SemiInboundService: elif isinstance(raw_range, str): time_range_str = raw_range - # 3. 处理数值和成本 in_qty = float(data.get('in_quantity') or 0) raw_cost = float(data.get('raw_material_cost') or 0) manual_cost = float(data.get('manual_cost') or 0) unit_total_cost = raw_cost + manual_cost total_value = unit_total_cost * in_qty - # 4. 获取全局打印流水号 next_global_id = 0 try: seq_sql = text("SELECT nextval('global_print_seq')") @@ -172,59 +185,47 @@ class SemiInboundService: final_sku = data.get('sku') if not final_sku: final_sku = generated_sku - final_barcode = data.get('barcode') if not final_barcode: final_barcode = final_sku arrival_list = data.get('arrival_photo', []) quality_report_list = data.get('quality_report_link', []) - if not isinstance(arrival_list, list): arrival_list = [] if not isinstance(quality_report_list, list): quality_report_list = [] - # 8. 创建记录 new_stock = StockSemi( base_id=material.id, global_print_id=next_global_id, sku=final_sku, - production_date=in_date_val, # 存入 DateTime - + production_date=in_date_val, serial_number=data.get('serial_number'), batch_number=data.get('batch_number'), barcode=final_barcode, - status='在库', quality_status=data.get('quality_status', '合格'), in_quantity=in_qty, stock_quantity=in_qty, available_quantity=in_qty, warehouse_location=data.get('warehouse_location'), - bom_code=data.get('bom_code'), bom_version=data.get('bom_version'), work_order_code=data.get('work_order_code'), production_manager=data.get('production_manager'), - production_start_time=p_start, production_end_time=p_end, production_time_range=time_range_str, - raw_material_cost=raw_cost, manual_cost=manual_cost, total_price=total_value, - arrival_photo=json.dumps(arrival_list), quality_report_link=json.dumps(quality_report_list), - detail_link=data.get('detail_link'), remark=data.get('remark') ) - db.session.add(new_stock) db.session.commit() return new_stock - except Exception as e: db.session.rollback() print("----- SemiInboundService Error -----") @@ -237,13 +238,11 @@ class SemiInboundService: @staticmethod def update_inbound(stock_id, data): from app.models.inbound.semi import StockSemi - try: stock = StockSemi.query.get(stock_id) if not stock: raise ValueError("记录不存在") - # --- [核心修改] 编辑时也要校验唯一性 --- new_base_id = data.get('base_id', stock.base_id) new_sn = data.get('serial_number', stock.serial_number) new_bn = data.get('batch_number', stock.batch_number) @@ -270,7 +269,6 @@ class SemiInboundService: 'detail_link': 'detail_link', 'remark': 'remark' } - for frontend_key, db_attr in field_mapping.items(): if frontend_key in data: setattr(stock, db_attr, data[frontend_key]) @@ -279,7 +277,6 @@ class SemiInboundService: imgs = data['arrival_photo'] if isinstance(imgs, list): stock.arrival_photo = json.dumps(imgs) - if 'quality_report_link' in data: imgs = data['quality_report_link'] if isinstance(imgs, list): @@ -294,7 +291,6 @@ class SemiInboundService: stock.production_start_time = None except: pass - if 'production_end_time' in data: try: if data['production_end_time']: @@ -304,7 +300,6 @@ class SemiInboundService: stock.production_end_time = None except: pass - if 'production_time_range' in data: raw_range = data['production_time_range'] if isinstance(raw_range, list): @@ -314,7 +309,6 @@ class SemiInboundService: qty_changed = False cost_changed = False - if 'in_quantity' in data: new_qty = float(data['in_quantity']) diff = new_qty - float(stock.in_quantity) @@ -323,22 +317,18 @@ class SemiInboundService: stock.stock_quantity = float(stock.stock_quantity) + diff stock.available_quantity = float(stock.available_quantity) + diff qty_changed = True - if 'raw_material_cost' in data: stock.raw_material_cost = float(data['raw_material_cost']) cost_changed = True - if 'manual_cost' in data: stock.manual_cost = float(data['manual_cost']) cost_changed = True - if cost_changed or qty_changed: unit_total = float(stock.raw_material_cost) + float(stock.manual_cost) stock.total_price = float(stock.in_quantity) * unit_total db.session.commit() return stock - except Exception as e: db.session.rollback() raise e @@ -365,7 +355,6 @@ class SemiInboundService: # ============================================================ @staticmethod def get_outbound_history(stock_id): - """获取出库历史""" try: records = TransOutbound.query.filter_by( source_table='stock_semi', stock_id=stock_id @@ -382,7 +371,6 @@ class SemiInboundService: from app.models.inbound.semi import StockSemi try: query = db.session.query(StockSemi).outerjoin(MaterialBase, StockSemi.base_id == MaterialBase.id) - if keyword: kw = f'%{keyword}%' query = query.filter( @@ -396,17 +384,12 @@ class SemiInboundService: StockSemi.bom_code.ilike(kw) ) ) - - # 类别筛选 if category and category.strip(): query = query.filter(MaterialBase.category == category.strip()) - # 类型筛选 if material_type and material_type.strip(): query = query.filter(MaterialBase.material_type == material_type.strip()) - if not statuses: statuses = ['在库', '借库'] - if '已出库' in statuses: query = query.filter(StockSemi.status.in_(statuses)) else: @@ -416,11 +399,8 @@ class SemiInboundService: StockSemi.stock_quantity > 0 ) ) - - # 按照 production_date (入库日期) 倒序排序 pagination = query.order_by(StockSemi.production_date.desc()).paginate(page=page, per_page=limit, error_out=False) - current_items = pagination.items def parse_img(json_str): @@ -432,10 +412,7 @@ class SemiInboundService: items = [] for item in current_items: - # [注意] 因为Model层已经修改了 to_dict 内部的 material -> base,所以这里直接调 to_dict 即可 d = item.to_dict() - - # 格式化展示日期 date_display = '' if item.production_date: try: @@ -443,30 +420,22 @@ class SemiInboundService: except: date_display = str(item.production_date)[:10] d['inbound_date'] = date_display - d['qty_stock'] = float(item.stock_quantity or 0) d['qty_available'] = float(item.available_quantity or 0) d['sum_stock'] = d['qty_stock'] d['sum_available'] = d['qty_available'] - d['arrival_photo'] = parse_img(item.arrival_photo) d['quality_report_link'] = parse_img(item.quality_report_link) d['global_print_id'] = item.global_print_id - items.append(d) - return {"total": pagination.total, "items": items} except Exception as e: print(f"List Error: {e}") traceback.print_exc() return {"total": 0, "items": []} - # ============================================================ - # 7. 系统用户搜索 - # ============================================================ @staticmethod def search_system_users(keyword): - """搜索系统用户(活跃状态)""" from app.models.system import SysUser try: query = SysUser.query.filter(SysUser.status == 'active') @@ -487,9 +456,6 @@ class SemiInboundService: except Exception: return [] - # ============================================================ - # 8. 获取筛选选项(类别、类型) - # ============================================================ @staticmethod def get_filter_options(): try: @@ -507,4 +473,4 @@ class SemiInboundService: except Exception: import traceback traceback.print_exc() - return {"categories": [], "types": []} + return {"categories": [], "types": []} \ No newline at end of file diff --git a/inventory-web/src/api/inbound/product.ts b/inventory-web/src/api/inbound/product.ts index d342203..7d1f76f 100644 --- a/inventory-web/src/api/inbound/product.ts +++ b/inventory-web/src/api/inbound/product.ts @@ -41,6 +41,15 @@ export function searchMaterialBase(keyword: string) { }) } +// 搜索BOM (新增) +export function searchBom(keyword: string) { + return request({ + url: '/inbound/product/search-bom', + method: 'get', + params: { keyword } + }) +} + // 用户建议 export function getUserSuggestions(params: any) { return request({ @@ -56,4 +65,4 @@ export function getFilterOptions() { url: '/inbound/product/options', method: 'get' }) -} +} \ No newline at end of file diff --git a/inventory-web/src/api/inbound/semi.ts b/inventory-web/src/api/inbound/semi.ts index 0a9a0c6..a077b85 100644 --- a/inventory-web/src/api/inbound/semi.ts +++ b/inventory-web/src/api/inbound/semi.ts @@ -44,6 +44,15 @@ export function searchMaterialBase(keyword: string) { }) } +// 5.5 搜索BOM (新增) +export function searchBom(keyword: string) { + return request({ + url: '/inbound/semi/search-bom', + method: 'get', + params: { keyword } + }) +} + // 用户建议 export function getUserSuggestions(params: any) { return request({ @@ -59,4 +68,4 @@ export function getFilterOptions() { url: '/inbound/semi/options', method: 'get' }) -} +} \ No newline at end of file diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue index f944356..27e4bc6 100644 --- a/inventory-web/src/views/stock/inbound/product.vue +++ b/inventory-web/src/views/stock/inbound/product.vue @@ -276,8 +276,36 @@
3. 生产与销售信息
- - + + + + + + {{ item.bom_no }} + + {{ item.version }} ({{ item.parent_spec }}) + + + + + + + @@ -353,10 +381,16 @@