diff --git a/inventory-backend/app/services/inbound/base_service.py b/inventory-backend/app/services/inbound/base_service.py index d40f7cb..2b2abe1 100644 --- a/inventory-backend/app/services/inbound/base_service.py +++ b/inventory-backend/app/services/inbound/base_service.py @@ -12,7 +12,9 @@ import traceback import json import io import datetime +import numpy as np from app.utils.ai_vision import extract_and_embed +from app.services.image_embedding_service import ImageEmbeddingService # 需要 pip install openpyxl from openpyxl import Workbook from openpyxl.styles import Font, Alignment, Border, Side, PatternFill @@ -556,10 +558,15 @@ class MaterialBaseService: product_image=json.dumps(data.get('generalImage', [])), is_enabled=is_enabled_val ) - # 实时提取产品图向量(失败不影响业务) - if new_material.product_image: - new_material.img_embedding = extract_and_embed(new_material.product_image) db.session.add(new_material) + db.session.flush() # 获取 new_material.id + + # 提取产品图向量到独立表(失败不影响业务) + image_list = data.get('generalImage', []) + if isinstance(image_list, list) and image_list: + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_MATERIAL_BASE, new_material.id, image_list + ) db.session.commit() return new_material @@ -588,11 +595,17 @@ class MaterialBaseService: if 'generalManual' in data: material.manual_link = json.dumps(data['generalManual']) if 'generalImage' in data: - material.product_image = json.dumps(data['generalImage']) - - # 补上这两行:提取新上传图片的向量! - if material.product_image: - material.img_embedding = extract_and_embed(material.product_image) + new_photo_list = data['generalImage'] + material.product_image = json.dumps(new_photo_list) + # 保存向量到独立表(全量替换) + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_MATERIAL_BASE, material.id, new_photo_list + ) + else: + material.product_image = None + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_MATERIAL_BASE, material.id + ) # 【核心修改】:兼容前端传来的布尔值 if 'isEnabled' in data: @@ -659,6 +672,10 @@ class MaterialBaseService: f"请先清理相关库存或仅‘禁用’此条目。" ) + # 删除时同步清理向量记录 + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_MATERIAL_BASE, material.id + ) db.session.delete(material) db.session.commit() return material_name diff --git a/inventory-backend/app/services/inbound/buy_service.py b/inventory-backend/app/services/inbound/buy_service.py index 2d3b070..f870cd7 100644 --- a/inventory-backend/app/services/inbound/buy_service.py +++ b/inventory-backend/app/services/inbound/buy_service.py @@ -9,7 +9,9 @@ from sqlalchemy import or_, func, text, and_ from sqlalchemy.exc import IntegrityError import traceback import json +import numpy as np from app.utils.ai_vision import extract_and_embed +from app.services.image_embedding_service import ImageEmbeddingService class BuyInboundService: @@ -178,10 +180,15 @@ class BuyInboundService: arrival_photo=json.dumps(data.get('arrival_photo', [])), inspection_report=json.dumps(data.get('inspection_report', [])) ) - # 实时提取到货图片向量(失败不影响业务) - if new_stock.arrival_photo: - new_stock.arrival_image_embedding = extract_and_embed(new_stock.arrival_photo) db.session.add(new_stock) + db.session.flush() # 获取 new_stock.id + + # 提取到货图片向量到新表(失败不影响业务) + photo_list = data.get('arrival_photo', []) + if isinstance(photo_list, list) and photo_list: + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_STOCK_BUY, new_stock.id, photo_list + ) db.session.commit() return new_stock except Exception as e: @@ -244,9 +251,19 @@ class BuyInboundService: for k, v in field_mapping.items(): if k in data: setattr(stock, v, data[k]) - if 'arrival_photo' in data: stock.arrival_photo = json.dumps(data['arrival_photo']) - if 'arrival_photo' in data and stock.arrival_photo: - stock.arrival_image_embedding = extract_and_embed(stock.arrival_photo) + if 'arrival_photo' in data: + new_photo_list = data['arrival_photo'] + stock.arrival_photo = json.dumps(new_photo_list) + # 保存向量到独立表(全量替换) + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_STOCK_BUY, stock.id, new_photo_list + ) + else: + stock.arrival_photo = None + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_STOCK_BUY, stock.id + ) + if 'inspection_report' in data: stock.inspection_report = json.dumps(data['inspection_report']) # 更新税率 @@ -289,8 +306,11 @@ class BuyInboundService: try: stock = StockBuy.query.get(stock_id) if not stock: raise ValueError("记录不存在") - # 提前获取物料名称用于审计日志(通过外键关系 base.name 获取) material_name = stock.base.name if stock.base else '未知物料' + # 删除时同步清理向量记录 + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_STOCK_BUY, stock.id + ) db.session.delete(stock) db.session.commit() return material_name diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py index 5d8523c..bda72f6 100644 --- a/inventory-backend/app/services/inbound/product_service.py +++ b/inventory-backend/app/services/inbound/product_service.py @@ -9,7 +9,9 @@ from sqlalchemy import or_, func, text, and_ from sqlalchemy.exc import IntegrityError import traceback import json +import numpy as np from app.utils.ai_vision import extract_and_embed +from app.services.image_embedding_service import ImageEmbeddingService class ProductInboundService: @@ -184,10 +186,14 @@ class ProductInboundService: sale_price=float(data.get('sale_price') or 0), order_id=data.get('order_id') ) - # 实时提取成品实拍图向量(失败不影响业务) - if new_stock.product_photo: - new_stock.arrival_image_embedding = extract_and_embed(new_stock.product_photo) db.session.add(new_stock) + db.session.flush() # 获取 new_stock.id + + # 提取产品图片向量到独立表(失败不影响业务) + if isinstance(photo_list, list) and photo_list: + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_STOCK_PRODUCT, new_stock.id, photo_list + ) db.session.commit() return new_stock except Exception as e: @@ -217,10 +223,17 @@ class ProductInboundService: if f in data: setattr(stock, f, data[f]) if 'product_photo' in data: - imgs = data['product_photo'] - if isinstance(imgs, list): stock.product_photo = json.dumps(imgs) - if stock.product_photo: - stock.arrival_image_embedding = extract_and_embed(stock.product_photo) + new_photo_list = data['product_photo'] + stock.product_photo = json.dumps(new_photo_list) + # 保存向量到独立表(全量替换) + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_STOCK_PRODUCT, stock.id, new_photo_list + ) + else: + stock.product_photo = None + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_STOCK_PRODUCT, stock.id + ) if 'quality_report_link' in data: imgs = data['quality_report_link'] if isinstance(imgs, list): stock.quality_report_link = json.dumps(imgs) @@ -261,8 +274,11 @@ class ProductInboundService: try: stock = StockProduct.query.get(stock_id) if stock: - # 提前获取物料名称用于审计日志(通过外键关系 base.name 获取) material_name = stock.base.name if stock.base else '未知物料' + # 删除时同步清理向量记录 + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_STOCK_PRODUCT, stock.id + ) db.session.delete(stock) db.session.commit() return material_name diff --git a/inventory-backend/app/services/inbound/semi_service.py b/inventory-backend/app/services/inbound/semi_service.py index 9fa9195..1778cdc 100644 --- a/inventory-backend/app/services/inbound/semi_service.py +++ b/inventory-backend/app/services/inbound/semi_service.py @@ -9,7 +9,9 @@ from sqlalchemy import or_, func, text, and_ from sqlalchemy.exc import IntegrityError import traceback import json +import numpy as np from app.utils.ai_vision import extract_and_embed +from app.services.image_embedding_service import ImageEmbeddingService class SemiInboundService: @@ -221,10 +223,14 @@ class SemiInboundService: detail_link=data.get('detail_link'), remark=data.get('remark') ) - # 实时提取到货图片向量(失败不影响业务) - if new_stock.arrival_photo: - new_stock.arrival_image_embedding = extract_and_embed(new_stock.arrival_photo) db.session.add(new_stock) + db.session.flush() # 获取 new_stock.id + + # 提取到货图片向量到独立表(失败不影响业务) + if isinstance(arrival_list, list) and arrival_list: + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_STOCK_SEMI, new_stock.id, arrival_list + ) db.session.commit() return new_stock except Exception as e: @@ -272,11 +278,17 @@ class SemiInboundService: setattr(stock, db_attr, data[frontend_key]) if 'arrival_photo' in data: - imgs = data['arrival_photo'] - if isinstance(imgs, list): - stock.arrival_photo = json.dumps(imgs) - if stock.arrival_photo: - stock.arrival_image_embedding = extract_and_embed(stock.arrival_photo) + new_photo_list = data['arrival_photo'] + stock.arrival_photo = json.dumps(new_photo_list) + # 保存向量到独立表(全量替换) + ImageEmbeddingService.save_embeddings( + ImageEmbeddingService.MODULE_STOCK_SEMI, stock.id, new_photo_list + ) + else: + stock.arrival_photo = None + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_STOCK_SEMI, stock.id + ) if 'quality_report_link' in data: imgs = data['quality_report_link'] if isinstance(imgs, list): @@ -350,8 +362,11 @@ class SemiInboundService: stock = StockSemi.query.get(stock_id) if not stock: raise ValueError("记录不存在") - # 提前获取物料名称用于审计日志(通过外键关系 base.name 获取) material_name = stock.base.name if stock.base else '未知物料' + # 删除时同步清理向量记录 + ImageEmbeddingService.delete_embeddings( + ImageEmbeddingService.MODULE_STOCK_SEMI, stock.id + ) db.session.delete(stock) db.session.commit() return material_name diff --git a/inventory-web/src/App.vue b/inventory-web/src/App.vue index d0ea334..637a2d0 100644 --- a/inventory-web/src/App.vue +++ b/inventory-web/src/App.vue @@ -239,7 +239,7 @@ const handleLogout = () => {