fix(backend): resolve DetachedInstanceError in audit_log, add pessimistic locks for stock adjustments, and eliminate N+1 queries with eager loading

This commit is contained in:
DXC
2026-04-02 18:44:12 +08:00
parent edf09508f6
commit a52ced0375
3 changed files with 39 additions and 13 deletions

View File

@ -3,6 +3,7 @@ from app.extensions import db, beijing_time
from datetime import datetime, timedelta
from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity
from app.utils.decorators import permission_required
from sqlalchemy.orm import joinedload
import uuid as uuid_module
import io
from openpyxl import Workbook
@ -954,8 +955,8 @@ def export_stocktake():
unscanned_items = []
# 遍历 StockBuy
for stock in StockBuy.query.all():
# ★ 修复 N+1 查询:使用 joinedload 预加载 base 关系
for stock in StockBuy.query.options(joinedload(StockBuy.base)).all():
key = ('stock_buy', stock.id)
if key in scanned_set:
continue
@ -966,7 +967,14 @@ def export_stocktake():
borrowed_qty = get_borrowed_qty('stock_buy', stock.id)
expected_qty = stock_qty - borrowed_qty
if expected_qty > 0:
mat_info = get_material_info('stock_buy', stock.id)
# ★ 直接使用预加载的 base 关系,避免额外查询
material = stock.base
mat_info = {
'name': material.name if material else '-',
'sku': getattr(stock, 'sku', None) or '-',
'spec': getattr(material, 'spec_model', None) if material else '-',
'location': getattr(stock, 'warehouse_location', None) or '-'
}
unscanned_items.append({
'name': mat_info['name'],
'sku': mat_info['sku'],
@ -980,7 +988,7 @@ def export_stocktake():
# 遍历 StockSemi
if StockSemi:
for stock in StockSemi.query.all():
for stock in StockSemi.query.options(joinedload(StockSemi.base)).all():
key = ('stock_semi', stock.id)
if key in scanned_set:
continue
@ -990,7 +998,14 @@ def export_stocktake():
borrowed_qty = get_borrowed_qty('stock_semi', stock.id)
expected_qty = stock_qty - borrowed_qty
if expected_qty > 0:
mat_info = get_material_info('stock_semi', stock.id)
# ★ 直接使用预加载的 base 关系,避免额外查询
material = stock.base
mat_info = {
'name': material.name if material else '-',
'sku': getattr(stock, 'sku', None) or '-',
'spec': getattr(material, 'spec_model', None) if material else '-',
'location': getattr(stock, 'warehouse_location', None) or '-'
}
unscanned_items.append({
'name': mat_info['name'],
'sku': mat_info['sku'],
@ -1004,7 +1019,7 @@ def export_stocktake():
# 遍历 StockProduct
if StockProduct:
for stock in StockProduct.query.all():
for stock in StockProduct.query.options(joinedload(StockProduct.base)).all():
key = ('stock_product', stock.id)
if key in scanned_set:
continue
@ -1014,7 +1029,14 @@ def export_stocktake():
borrowed_qty = get_borrowed_qty('stock_product', stock.id)
expected_qty = stock_qty - borrowed_qty
if expected_qty > 0:
mat_info = get_material_info('stock_product', stock.id)
# ★ 直接使用预加载的 base 关系,避免额外查询
material = stock.base
mat_info = {
'name': material.name if material else '-',
'sku': getattr(stock, 'sku', None) or '-',
'spec': getattr(material, 'spec_model', None) if material else '-',
'location': getattr(stock, 'warehouse_location', None) or '-'
}
unscanned_items.append({
'name': mat_info['name'],
'sku': mat_info['sku'],