|
|
|
|
@ -1,6 +1,9 @@
|
|
|
|
|
from app.extensions import db
|
|
|
|
|
from app.models.inbound.buy import StockBuy
|
|
|
|
|
from app.models.material import MaterialBase
|
|
|
|
|
# ==============================================================================
|
|
|
|
|
# ✅ 修复点:基础物料模型实际位于 app/models/base.py
|
|
|
|
|
# ==============================================================================
|
|
|
|
|
from app.models.base import MaterialBase
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from sqlalchemy import or_, func
|
|
|
|
|
import traceback
|
|
|
|
|
@ -9,16 +12,23 @@ import traceback
|
|
|
|
|
class BuyInboundService:
|
|
|
|
|
@staticmethod
|
|
|
|
|
def search_base_material(keyword):
|
|
|
|
|
"""
|
|
|
|
|
搜索基础物料
|
|
|
|
|
如果 keyword 为空,返回最新的 20 条记录
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
if not keyword:
|
|
|
|
|
return []
|
|
|
|
|
query = MaterialBase.query.filter(
|
|
|
|
|
MaterialBase.is_enabled == True,
|
|
|
|
|
or_(
|
|
|
|
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
|
|
|
|
MaterialBase.spec_model.ilike(f'%{keyword}%')
|
|
|
|
|
query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
|
|
|
|
|
|
|
|
|
|
if keyword:
|
|
|
|
|
query = query.filter(
|
|
|
|
|
or_(
|
|
|
|
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
|
|
|
|
MaterialBase.spec_model.ilike(f'%{keyword}%')
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
).limit(20)
|
|
|
|
|
|
|
|
|
|
# 无论是否有关键词,都按 ID 倒序排列,取前 20 条
|
|
|
|
|
query = query.order_by(MaterialBase.id.desc()).limit(20)
|
|
|
|
|
|
|
|
|
|
results = []
|
|
|
|
|
for item in query.all():
|
|
|
|
|
@ -50,10 +60,11 @@ class BuyInboundService:
|
|
|
|
|
in_date_val = datetime.utcnow().date()
|
|
|
|
|
if data.get('in_date'):
|
|
|
|
|
try:
|
|
|
|
|
if len(str(data['in_date'])) > 10:
|
|
|
|
|
in_date_val = datetime.strptime(str(data['in_date'])[:10], '%Y-%m-%d').date()
|
|
|
|
|
else:
|
|
|
|
|
in_date_val = datetime.strptime(str(data['in_date']), '%Y-%m-%d').date()
|
|
|
|
|
# 兼容 YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD
|
|
|
|
|
date_str = str(data['in_date'])
|
|
|
|
|
if len(date_str) > 10:
|
|
|
|
|
date_str = date_str[:10]
|
|
|
|
|
in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
|
|
|
except ValueError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@ -82,8 +93,7 @@ class BuyInboundService:
|
|
|
|
|
buyer_email=data.get('purchaser_email'),
|
|
|
|
|
original_link=data.get('source_link'),
|
|
|
|
|
detail_link=data.get('detail_link'),
|
|
|
|
|
arrival_photo=data.get('arrival_photo'),
|
|
|
|
|
remark=data.get('remark')
|
|
|
|
|
arrival_photo=data.get('arrival_photo')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
db.session.add(new_stock)
|
|
|
|
|
@ -98,7 +108,6 @@ class BuyInboundService:
|
|
|
|
|
def update_inbound(stock_id, data):
|
|
|
|
|
try:
|
|
|
|
|
print(f"----- UPDATE DEBUG: ID={stock_id} -----")
|
|
|
|
|
print(f"Payload: {data}")
|
|
|
|
|
|
|
|
|
|
stock = StockBuy.query.get(stock_id)
|
|
|
|
|
if not stock:
|
|
|
|
|
@ -115,7 +124,6 @@ class BuyInboundService:
|
|
|
|
|
'supplier_name': 'supplier_name',
|
|
|
|
|
'detail_link': 'detail_link',
|
|
|
|
|
'arrival_photo': 'arrival_photo',
|
|
|
|
|
'remark': 'remark',
|
|
|
|
|
'currency': 'currency',
|
|
|
|
|
'exchange_rate': 'exchange_rate',
|
|
|
|
|
'purchaser': 'buyer_name',
|
|
|
|
|
@ -193,16 +201,13 @@ class BuyInboundService:
|
|
|
|
|
pagination = query.order_by(StockBuy.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
# 新增逻辑:计算总库存
|
|
|
|
|
# 计算总库存 (聚合)
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
# 2. 提取当前页所有涉及的 base_id
|
|
|
|
|
current_items = pagination.items
|
|
|
|
|
base_ids = list(set([item.base_id for item in current_items if item.base_id]))
|
|
|
|
|
|
|
|
|
|
# 3. 聚合查询:一次性查出这些 base_id 对应的 stock_quantity 和 available_quantity 总和
|
|
|
|
|
stock_map = {}
|
|
|
|
|
if base_ids:
|
|
|
|
|
# SELECT base_id, SUM(stock_quantity), SUM(available_quantity) FROM stock_buy WHERE base_id IN (...) GROUP BY base_id
|
|
|
|
|
aggregates = db.session.query(
|
|
|
|
|
StockBuy.base_id,
|
|
|
|
|
func.sum(StockBuy.stock_quantity).label('total_stock'),
|
|
|
|
|
@ -214,7 +219,6 @@ class BuyInboundService:
|
|
|
|
|
'total_stock': float(agg.total_stock or 0),
|
|
|
|
|
'total_avail': float(agg.total_avail or 0)
|
|
|
|
|
}
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
items = []
|
|
|
|
|
for item in current_items:
|
|
|
|
|
@ -224,7 +228,6 @@ class BuyInboundService:
|
|
|
|
|
mat_unit = item.material.unit if item.material else ''
|
|
|
|
|
mat_type = item.material.material_type if item.material else ''
|
|
|
|
|
|
|
|
|
|
# 获取该物料的统计数据
|
|
|
|
|
stats = stock_map.get(item.base_id, {'total_stock': 0, 'total_avail': 0})
|
|
|
|
|
|
|
|
|
|
d = {
|
|
|
|
|
@ -244,12 +247,10 @@ class BuyInboundService:
|
|
|
|
|
'status': item.status,
|
|
|
|
|
'inspection_status': item.inspection_status,
|
|
|
|
|
|
|
|
|
|
# --- 原始批次数据 (用于编辑) ---
|
|
|
|
|
'qty_inbound': float(item.in_quantity or 0),
|
|
|
|
|
'qty_stock': float(item.stock_quantity or 0),
|
|
|
|
|
'qty_available': float(item.available_quantity or 0),
|
|
|
|
|
|
|
|
|
|
# --- [新增] 聚合统计数据 (用于列表显示) ---
|
|
|
|
|
'sum_stock': stats['total_stock'],
|
|
|
|
|
'sum_available': stats['total_avail'],
|
|
|
|
|
|
|
|
|
|
@ -263,8 +264,7 @@ class BuyInboundService:
|
|
|
|
|
'purchaser_email': item.buyer_email,
|
|
|
|
|
'source_link': item.original_link,
|
|
|
|
|
'detail_link': item.detail_link,
|
|
|
|
|
'arrival_photo': item.arrival_photo,
|
|
|
|
|
'remark': item.remark
|
|
|
|
|
'arrival_photo': item.arrival_photo
|
|
|
|
|
}
|
|
|
|
|
items.append(d)
|
|
|
|
|
|
|
|
|
|
|