feat: improve fuzzy search for buy inbound material

Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
dxc
2026-02-11 11:28:15 +08:00
parent b1e2836e4b
commit d0a237625c

View File

@ -41,7 +41,7 @@ class BuyInboundService:
raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复录入,可直接在该批次下追加库存。")
# ============================================================
# 1. 基础物料搜索 (参照 Base 模块:全量模糊匹配)
# 1. 基础物料搜索 (增强模糊匹配:多字段、多关键词、分词匹配)
# ============================================================
@staticmethod
def search_base_material(keyword):
@ -49,26 +49,56 @@ class BuyInboundService:
query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
if keyword:
# [参照 Base 逻辑] 简单直接的模糊匹配LIKE %keyword%
k_str = f'%{keyword.strip()}%'
# 1. 清理关键词:去除首尾空格,并将多个连续空格替换为单个
import re
keyword_clean = re.sub(r'\s+', ' ', keyword.strip())
# 2. 支持两种搜索模式:
# a) 精确短语匹配:用双引号包裹,如 "蓝色电阻"
# b) 多关键词 AND 匹配:空格分隔,如 "蓝色 电阻"
# c) 单关键词模糊匹配:如 "蓝色"
if keyword_clean.startswith('"') and keyword_clean.endswith('"'):
# 精确短语匹配
exact_phrase = keyword_clean[1:-1]
if exact_phrase:
k_str = f'%{exact_phrase}%'
conditions = [
MaterialBase.name.ilike(k_str),
MaterialBase.spec_model.ilike(k_str),
MaterialBase.pinyin.ilike(k_str),
MaterialBase.category.ilike(k_str),
MaterialBase.material_type.ilike(k_str)
]
if hasattr(MaterialBase, 'brand'):
conditions.append(MaterialBase.brand.ilike(k_str))
if hasattr(MaterialBase, 'manufacturer'):
conditions.append(MaterialBase.manufacturer.ilike(k_str))
query = query.filter(or_(*conditions))
else:
# 多关键词 AND 匹配
keywords = keyword_clean.split()
if keywords:
and_conditions = []
for word in keywords:
k_str = f'%{word}%'
word_conditions = [
MaterialBase.name.ilike(k_str),
MaterialBase.spec_model.ilike(k_str),
MaterialBase.pinyin.ilike(k_str),
MaterialBase.category.ilike(k_str),
MaterialBase.material_type.ilike(k_str)
]
if hasattr(MaterialBase, 'brand'):
word_conditions.append(MaterialBase.brand.ilike(k_str))
if hasattr(MaterialBase, 'manufacturer'):
word_conditions.append(MaterialBase.manufacturer.ilike(k_str))
and_conditions.append(or_(*word_conditions))
# 使用 AND 连接所有关键词条件
if and_conditions:
query = query.filter(and_(*and_conditions))
conditions = [
MaterialBase.name.ilike(k_str), # 名称
MaterialBase.spec_model.ilike(k_str), # 规格
MaterialBase.pinyin.ilike(k_str), # 拼音
MaterialBase.category.ilike(k_str), # 类别
MaterialBase.material_type.ilike(k_str) # 类型
]
# 安全地添加可能存在的扩展字段 (品牌/厂家)
if hasattr(MaterialBase, 'brand'):
conditions.append(MaterialBase.brand.ilike(k_str))
if hasattr(MaterialBase, 'manufacturer'):
conditions.append(MaterialBase.manufacturer.ilike(k_str))
query = query.filter(or_(*conditions))
# [参照 Base 逻辑] 移除 limit返回所有结果
# 按 ID 倒序排序
query = query.order_by(MaterialBase.id.desc())
results = []
@ -80,7 +110,6 @@ class BuyInboundService:
'category': item.category,
'unit': item.unit,
'type': item.material_type,
# 使用 getattr 防止字段不存在报错
'brand': getattr(item, 'brand', ''),
'manufacturer': getattr(item, 'manufacturer', ''),
'pinyin': getattr(item, 'pinyin', ''),
@ -261,7 +290,7 @@ class BuyInboundService:
raise e
# ============================================================
# 5. 获取列表 (参照 Base 逻辑:全量模糊匹配)
# 5. 获取列表 (增强模糊匹配:多字段、多关键词、分词匹配)
# ============================================================
@staticmethod
def get_list(page, limit, keyword=None, statuses=None):
@ -269,30 +298,58 @@ class BuyInboundService:
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
if keyword:
# [简单匹配] 只要任一字段包含该字符串,即匹配
k_str = f'%{keyword.strip()}%'
conditions = [
# 库存业务字段
StockBuy.sku.ilike(k_str),
StockBuy.barcode.ilike(k_str),
StockBuy.batch_number.ilike(k_str),
StockBuy.serial_number.ilike(k_str),
StockBuy.supplier_name.ilike(k_str),
StockBuy.buyer_name.ilike(k_str),
# 基础物料关联字段
MaterialBase.name.ilike(k_str),
MaterialBase.spec_model.ilike(k_str),
MaterialBase.pinyin.ilike(k_str),
MaterialBase.category.ilike(k_str)
]
if hasattr(MaterialBase, 'brand'):
conditions.append(MaterialBase.brand.ilike(k_str))
if hasattr(MaterialBase, 'manufacturer'):
conditions.append(MaterialBase.manufacturer.ilike(k_str))
query = query.filter(or_(*conditions))
# 1. 清理关键词
import re
keyword_clean = re.sub(r'\s+', ' ', keyword.strip())
if keyword_clean.startswith('"') and keyword_clean.endswith('"'):
# 精确短语匹配
exact_phrase = keyword_clean[1:-1]
if exact_phrase:
k_str = f'%{exact_phrase}%'
conditions = [
StockBuy.sku.ilike(k_str),
StockBuy.barcode.ilike(k_str),
StockBuy.batch_number.ilike(k_str),
StockBuy.serial_number.ilike(k_str),
StockBuy.supplier_name.ilike(k_str),
StockBuy.buyer_name.ilike(k_str),
MaterialBase.name.ilike(k_str),
MaterialBase.spec_model.ilike(k_str),
MaterialBase.pinyin.ilike(k_str),
MaterialBase.category.ilike(k_str)
]
if hasattr(MaterialBase, 'brand'):
conditions.append(MaterialBase.brand.ilike(k_str))
if hasattr(MaterialBase, 'manufacturer'):
conditions.append(MaterialBase.manufacturer.ilike(k_str))
query = query.filter(or_(*conditions))
else:
# 多关键词 AND 匹配
keywords = keyword_clean.split()
if keywords:
and_conditions = []
for word in keywords:
k_str = f'%{word}%'
word_conditions = [
StockBuy.sku.ilike(k_str),
StockBuy.barcode.ilike(k_str),
StockBuy.batch_number.ilike(k_str),
StockBuy.serial_number.ilike(k_str),
StockBuy.supplier_name.ilike(k_str),
StockBuy.buyer_name.ilike(k_str),
MaterialBase.name.ilike(k_str),
MaterialBase.spec_model.ilike(k_str),
MaterialBase.pinyin.ilike(k_str),
MaterialBase.category.ilike(k_str)
]
if hasattr(MaterialBase, 'brand'):
word_conditions.append(MaterialBase.brand.ilike(k_str))
if hasattr(MaterialBase, 'manufacturer'):
word_conditions.append(MaterialBase.manufacturer.ilike(k_str))
and_conditions.append(or_(*word_conditions))
if and_conditions:
query = query.filter(and_(*and_conditions))
if not statuses:
statuses = ['在库', '借库']
@ -447,4 +504,4 @@ class BuyInboundService:
locs = [row[0] for row in query.all()]
return locs
except Exception:
return []
return []