perf: 系统级性能优化与并发安全修复
## 并发安全修复 (4处) - scrap.py: 报废执行添加 SELECT FOR UPDATE 悲观锁,消除 TOCTOU 竞态 - stock.py (adjust_stock): 盘点调整添加 for_update=True 行锁 - outbound_service.py: 低库存预警 SMTP 调用移到 commit 之后,避免长事务 - trans_service.py: execute_dispatch 按 (source_table, id) 排序 items,消除死锁风险 ## N+1 查询优化 (2处) - inventory_task.py: _prefetch_inventory_map 单条 UNION ALL+GROUP BY 替代循环内逐条查询(N*4次→2次) - stock.py (export_stocktake): get_borrowed_qty 批量 GROUP BY 替代逐条 TransBorrow 查询(~18000次→1次) ## BOM 列表性能重构 - bom_service.py: get_bom_list 单条 GROUP BY+string_agg+分页,消除 N+1 循环查询 - bom_service.py: 新增 get_bom_summary (轻量 GROUP BY category+COUNT) - bom.py: 新增 /api/v1/bom/summary 路由,/list 支持 category 过滤 ## Odoo 基础信息懒加载 - base_service.py: 新增 get_odoo_summary (GROUP BY category+COUNT) - base.py: 新增 /api/v1/inbound/base/odoo-summary 路由 - buyOdoo.vue: 懒加载分组架构 (fetchOdooSummary + loadGroupItems) - material_base.ts: 新增 getOdooSummary API ## 前端 Bug 修复 - BomManage.vue: 懒加载分组 (fetchBomSummary + loadGroupItems + collapse) - BomManage.vue: 适配新 API 格式 (res.data.items 替代 res.data) - buyOdoo.vue: 移除 "点击展开加载" 文字 - Selection.vue + borrow/apply/index.vue: openBomSelect 适配新 API 格式
This commit is contained in:
@ -476,6 +476,62 @@ class MaterialBaseService:
|
||||
print(f"查询基础信息列表失败: {e}")
|
||||
return {"total": 0, "items": []}
|
||||
|
||||
@staticmethod
|
||||
def get_odoo_summary(keyword=None, is_enabled=None):
|
||||
"""
|
||||
Odoo 分组摘要 API(极轻量,不 JOIN 任何库存表)
|
||||
|
||||
执行:
|
||||
SELECT category, COUNT(id) AS count
|
||||
FROM material_base
|
||||
WHERE ...
|
||||
GROUP BY category
|
||||
ORDER BY category
|
||||
|
||||
返回:
|
||||
[{"category": "IRIS/半成品/高塔监测T", "count": 54}, ...]
|
||||
"""
|
||||
try:
|
||||
query = db.session.query(
|
||||
MaterialBase.category,
|
||||
func.count(MaterialBase.id).label('count')
|
||||
)
|
||||
|
||||
# 状态过滤
|
||||
if is_enabled is not None:
|
||||
query = query.filter(MaterialBase.is_enabled == is_enabled)
|
||||
|
||||
# 关键词搜索(与 get_list 行为一致)
|
||||
if keyword:
|
||||
kw = f'%{keyword.strip()}%'
|
||||
query = query.filter(or_(
|
||||
MaterialBase.name.ilike(kw),
|
||||
MaterialBase.common_name.ilike(kw),
|
||||
MaterialBase.spec_model.ilike(kw)
|
||||
))
|
||||
|
||||
# 行级数据隔离
|
||||
from app.utils.decorators import get_current_company_filter
|
||||
company_limit = get_current_company_filter()
|
||||
if company_limit is not None:
|
||||
query = query.filter(MaterialBase.company_name == company_limit)
|
||||
|
||||
# GROUP BY + ORDER
|
||||
query = query.group_by(MaterialBase.category) \
|
||||
.order_by(MaterialBase.category)
|
||||
|
||||
rows = query.all()
|
||||
|
||||
return [
|
||||
{"category": row.category or "未分类", "count": row.count}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
print(f"查询 Odoo 摘要失败: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_distinct_options():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user