fix: correct SQLAlchemy join condition to resolve MaterialBase AttributeError

This commit is contained in:
DXC
2026-03-20 10:06:22 +08:00
parent 990399a408
commit 34629b432a
2 changed files with 114 additions and 27 deletions

View File

@ -1,6 +1,6 @@
import uuid # .material -> .base refactor checked import uuid # .material -> .base refactor checked
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
from sqlalchemy import or_, func, desc from sqlalchemy import or_, func, desc, and_
from app.extensions import db from app.extensions import db
from app.models.outbound import TransOutbound from app.models.outbound import TransOutbound
@ -197,26 +197,70 @@ class OutboundService:
查询出库记录(按出库单号分组),包含详细物品信息 查询出库记录(按出库单号分组),包含详细物品信息
支持跨表搜索单号、领用人、SKU、物料名称、规格型号 支持跨表搜索单号、领用人、SKU、物料名称、规格型号
""" """
# 1. 查询分页单号 # 1. 构建基础查询
# 如果有关键词,需要联表搜索物料名称和规格型号 # 如果有关键词,需要联表搜索物料名称和规格型号
if keyword: if keyword:
# 子查询:关联 material_base 表获取物料名称和规格型号 # 由于 TransOutbound 通过 source_table + stock_id 关联到不同库存表,再关联到 MaterialBase
material_join = db.session.query( # 需要使用 union 或分别查询后合并
TransOutbound.outbound_no # 方案:分别查询三种来源的 matching outbound_no,然后合并
# 查询 stock_buy 路径匹配的名称/规格
buy_match = db.session.query(TransOutbound.outbound_no).join(
StockBuy, and_(
TransOutbound.stock_id == StockBuy.id,
TransOutbound.source_table == 'stock_buy'
)
).join( ).join(
MaterialBase, MaterialBase, StockBuy.base_id == MaterialBase.id
TransOutbound.sku == MaterialBase.sku ).filter(
).filter(or_( or_(
MaterialBase.name.ilike(f'%{keyword}%'), MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%') MaterialBase.spec_model.ilike(f'%{keyword}%')
)).subquery() )
).subquery()
# 主搜索条件单号、领用人、SKU + 物料名称规格型号 # 查询 stock_semi 路径匹配的名称/规格
semi_match = db.session.query(TransOutbound.outbound_no).join(
StockSemi, and_(
TransOutbound.stock_id == StockSemi.id,
TransOutbound.source_table == 'stock_semi'
)
).join(
MaterialBase, StockSemi.base_id == MaterialBase.id
).filter(
or_(
MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%')
)
).subquery()
# 查询 stock_product 路径匹配的名称/规格
product_match = db.session.query(TransOutbound.outbound_no).join(
StockProduct, and_(
TransOutbound.stock_id == StockProduct.id,
TransOutbound.source_table == 'stock_product'
)
).join(
MaterialBase, StockProduct.base_id == MaterialBase.id
).filter(
or_(
MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%')
)
).subquery()
# 合并三种来源的匹配单号
all_matches = db.session.query(buy_match.outbound_no).union(
db.session.query(semi_match.outbound_no),
db.session.query(product_match.outbound_no)
).subquery()
# 主搜索条件单号、领用人、SKU + 物料名称/规格匹配的单号
keyword_conditions = or_( keyword_conditions = or_(
TransOutbound.outbound_no.ilike(f'%{keyword}%'), TransOutbound.outbound_no.ilike(f'%{keyword}%'),
TransOutbound.consumer_name.ilike(f'%{keyword}%'), TransOutbound.consumer_name.ilike(f'%{keyword}%'),
TransOutbound.sku.ilike(f'%{keyword}%'), TransOutbound.sku.ilike(f'%{keyword}%'),
TransOutbound.outbound_no.in_(material_join) # 匹配物料名称/规格型号的单号 TransOutbound.outbound_no.in_(all_matches)
) )
else: else:
keyword_conditions = None keyword_conditions = None

View File

@ -6,7 +6,7 @@ from app.models.inbound.buy import StockBuy
from app.models.inbound.semi import StockSemi from app.models.inbound.semi import StockSemi
from app.models.inbound.product import StockProduct from app.models.inbound.product import StockProduct
from app.models.base import MaterialBase from app.models.base import MaterialBase
from sqlalchemy import desc, func, nullslast, asc, or_ from sqlalchemy import desc, func, nullslast, asc, or_, and_
class TransService: class TransService:
@ -193,23 +193,66 @@ class TransService:
# 如果有关键词,需要联表搜索物料名称和规格型号 # 如果有关键词,需要联表搜索物料名称和规格型号
if keyword: if keyword:
# 子查询:关联 material_base 表获取物料名称和规格型号 # TransBorrow 通过 source_table + stock_id 关联到不同库存表,再关联到 MaterialBase
material_join = db.session.query( # 需要使用 union 或分别查询后合并
TransBorrow.id
# 查询 stock_buy 路径匹配的名称/规格
buy_match = db.session.query(TransBorrow.id).join(
StockBuy, and_(
TransBorrow.stock_id == StockBuy.id,
TransBorrow.source_table == 'stock_buy'
)
).join( ).join(
MaterialBase, MaterialBase, StockBuy.base_id == MaterialBase.id
TransBorrow.sku == MaterialBase.sku ).filter(
).filter(or_( or_(
MaterialBase.name.ilike(f'%{keyword}%'), MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%') MaterialBase.spec_model.ilike(f'%{keyword}%')
)).subquery() )
).subquery()
# 主搜索条件借用人、SKU、单号 + 物料名称规格型号 # 查询 stock_semi 路径匹配的名称/规格
semi_match = db.session.query(TransBorrow.id).join(
StockSemi, and_(
TransBorrow.stock_id == StockSemi.id,
TransBorrow.source_table == 'stock_semi'
)
).join(
MaterialBase, StockSemi.base_id == MaterialBase.id
).filter(
or_(
MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%')
)
).subquery()
# 查询 stock_product 路径匹配的名称/规格
product_match = db.session.query(TransBorrow.id).join(
StockProduct, and_(
TransBorrow.stock_id == StockProduct.id,
TransBorrow.source_table == 'stock_product'
)
).join(
MaterialBase, StockProduct.base_id == MaterialBase.id
).filter(
or_(
MaterialBase.name.ilike(f'%{keyword}%'),
MaterialBase.spec_model.ilike(f'%{keyword}%')
)
).subquery()
# 合并三种来源的匹配 ID
all_matches = db.session.query(buy_match.id).union(
db.session.query(semi_match.id),
db.session.query(product_match.id)
).subquery()
# 主搜索条件借用人、SKU、单号 + 物料名称/规格匹配
keyword_conditions = or_( keyword_conditions = or_(
TransBorrow.borrower_name.ilike(f'%{keyword}%'), TransBorrow.borrower_name.ilike(f'%{keyword}%'),
TransBorrow.sku.ilike(f'%{keyword}%'), TransBorrow.sku.ilike(f'%{keyword}%'),
TransBorrow.borrow_no.ilike(f'%{keyword}%'), TransBorrow.borrow_no.ilike(f'%{keyword}%'),
TransBorrow.id.in_(material_join) # 匹配物料名称/规格型号的记录 TransBorrow.id.in_(all_matches)
) )
q = q.filter(keyword_conditions) q = q.filter(keyword_conditions)