2026-01-28 17:44:39 +08:00
|
|
|
|
# 文件路径: app/services/inbound/base_service.py
|
|
|
|
|
|
|
2026-01-28 08:54:11 +08:00
|
|
|
|
from app.extensions import db
|
2026-01-30 11:50:35 +08:00
|
|
|
|
from app.models.base import MaterialBase
|
2026-02-04 14:29:59 +08:00
|
|
|
|
from app.models.inbound.buy import StockBuy
|
|
|
|
|
|
from app.models.inbound.semi import StockSemi
|
2026-01-28 08:54:11 +08:00
|
|
|
|
from sqlalchemy import or_
|
|
|
|
|
|
import traceback
|
2026-02-09 16:08:47 +08:00
|
|
|
|
import json
|
2026-01-28 08:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MaterialBaseService:
|
2026-01-28 17:44:39 +08:00
|
|
|
|
"""
|
|
|
|
|
|
基础物料服务层
|
|
|
|
|
|
负责处理 MaterialBase 的增删改查及搜索逻辑
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def search_material(keyword):
|
|
|
|
|
|
"""
|
|
|
|
|
|
根据关键字搜索已启用的基础物料
|
|
|
|
|
|
(供 /api/v1/inbound/base/search 接口调用)
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
if not keyword:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
query = MaterialBase.query.filter(
|
|
|
|
|
|
MaterialBase.is_enabled == True,
|
|
|
|
|
|
or_(
|
|
|
|
|
|
MaterialBase.name.ilike(f'%{keyword}%'),
|
2026-02-04 14:29:59 +08:00
|
|
|
|
MaterialBase.common_name.ilike(f'%{keyword}%'),
|
2026-01-28 17:44:39 +08:00
|
|
|
|
MaterialBase.spec_model.ilike(f'%{keyword}%')
|
|
|
|
|
|
)
|
|
|
|
|
|
).limit(20)
|
|
|
|
|
|
|
|
|
|
|
|
results = []
|
|
|
|
|
|
for item in query.all():
|
|
|
|
|
|
results.append({
|
|
|
|
|
|
'id': item.id,
|
|
|
|
|
|
'name': item.name,
|
2026-02-09 16:08:47 +08:00
|
|
|
|
'commonName': item.common_name,
|
2026-01-28 17:44:39 +08:00
|
|
|
|
'spec': item.spec_model,
|
|
|
|
|
|
'category': item.category,
|
|
|
|
|
|
'unit': item.unit,
|
|
|
|
|
|
'type': item.material_type,
|
|
|
|
|
|
'status': '启用'
|
|
|
|
|
|
})
|
|
|
|
|
|
return results
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
2026-01-28 08:54:11 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def get_list(page, limit, filters=None):
|
|
|
|
|
|
"""
|
2026-01-28 17:44:39 +08:00
|
|
|
|
获取基础信息列表 (带分页和筛选)
|
2026-01-28 08:54:11 +08:00
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
query = MaterialBase.query
|
|
|
|
|
|
|
|
|
|
|
|
if filters:
|
2026-02-04 14:29:59 +08:00
|
|
|
|
# 1. 关键词模糊搜索 (名称 或 俗名 或 规格型号)
|
2026-01-28 08:54:11 +08:00
|
|
|
|
if filters.get('keyword'):
|
|
|
|
|
|
kw = f"%{filters['keyword']}%"
|
|
|
|
|
|
query = query.filter(or_(
|
2026-02-04 14:29:59 +08:00
|
|
|
|
MaterialBase.name.ilike(kw),
|
|
|
|
|
|
MaterialBase.common_name.ilike(kw),
|
|
|
|
|
|
MaterialBase.spec_model.ilike(kw)
|
2026-01-28 08:54:11 +08:00
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 精确筛选
|
|
|
|
|
|
if filters.get('category'):
|
|
|
|
|
|
query = query.filter_by(category=filters['category'])
|
|
|
|
|
|
|
|
|
|
|
|
if filters.get('type'):
|
|
|
|
|
|
query = query.filter_by(material_type=filters['type'])
|
|
|
|
|
|
|
|
|
|
|
|
if filters.get('isEnabled') is not None:
|
|
|
|
|
|
# 前端传 1/0,转为 Boolean
|
|
|
|
|
|
is_active = bool(int(filters['isEnabled']))
|
|
|
|
|
|
query = query.filter_by(is_enabled=is_active)
|
|
|
|
|
|
|
|
|
|
|
|
# 按 ID 倒序排列
|
|
|
|
|
|
pagination = query.order_by(MaterialBase.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
|
|
|
|
|
|
|
|
|
|
|
items = [item.to_dict() for item in pagination.items]
|
|
|
|
|
|
return {"total": pagination.total, "items": items}
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"查询基础信息列表失败: {e}")
|
|
|
|
|
|
return {"total": 0, "items": []}
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def create_material(data):
|
|
|
|
|
|
"""新增基础信息"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 0. 基础校验
|
|
|
|
|
|
if not data.get('name') or not data.get('spec'):
|
|
|
|
|
|
raise ValueError("名称和规格型号不能为空")
|
|
|
|
|
|
|
2026-02-09 16:08:47 +08:00
|
|
|
|
# 1. 查重
|
2026-01-28 08:54:11 +08:00
|
|
|
|
exist = MaterialBase.query.filter_by(
|
|
|
|
|
|
name=data['name'],
|
|
|
|
|
|
spec_model=data['spec']
|
|
|
|
|
|
).first()
|
|
|
|
|
|
if exist:
|
|
|
|
|
|
raise ValueError(f"已存在相同名称和规格的数据 (ID: {exist.id})")
|
|
|
|
|
|
|
2026-02-09 16:08:47 +08:00
|
|
|
|
# 2. 创建对象 (列表转JSON字符串)
|
2026-01-28 08:54:11 +08:00
|
|
|
|
new_material = MaterialBase(
|
|
|
|
|
|
name=data['name'],
|
2026-02-09 16:08:47 +08:00
|
|
|
|
common_name=data.get('commonName'),
|
2026-01-28 17:44:39 +08:00
|
|
|
|
spec_model=data['spec'],
|
2026-01-28 08:54:11 +08:00
|
|
|
|
category=data.get('category'),
|
2026-01-28 17:44:39 +08:00
|
|
|
|
material_type=data.get('type'),
|
2026-01-28 08:54:11 +08:00
|
|
|
|
unit=data.get('unit'),
|
2026-01-28 17:44:39 +08:00
|
|
|
|
visibility_level=data.get('visibilityLevel'),
|
2026-02-09 16:08:47 +08:00
|
|
|
|
# 修改:将列表 dumps 为字符串
|
|
|
|
|
|
manual_link=json.dumps(data.get('generalManual', [])),
|
|
|
|
|
|
product_image=json.dumps(data.get('generalImage', [])),
|
2026-01-28 08:54:11 +08:00
|
|
|
|
is_enabled=True if data.get('isEnabled', 1) == 1 else False
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
db.session.add(new_material)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
return new_material
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def update_material(m_id, data):
|
|
|
|
|
|
"""修改基础信息"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
material = MaterialBase.query.get(m_id)
|
|
|
|
|
|
if not material:
|
|
|
|
|
|
raise ValueError("数据不存在")
|
|
|
|
|
|
|
2026-01-28 17:44:39 +08:00
|
|
|
|
# 更新字段
|
2026-01-28 08:54:11 +08:00
|
|
|
|
if 'name' in data: material.name = data['name']
|
2026-02-09 16:08:47 +08:00
|
|
|
|
if 'commonName' in data: material.common_name = data['commonName']
|
2026-01-28 08:54:11 +08:00
|
|
|
|
if 'spec' in data: material.spec_model = data['spec']
|
|
|
|
|
|
if 'category' in data: material.category = data['category']
|
|
|
|
|
|
if 'type' in data: material.material_type = data['type']
|
|
|
|
|
|
if 'unit' in data: material.unit = data['unit']
|
|
|
|
|
|
if 'visibilityLevel' in data: material.visibility_level = data['visibilityLevel']
|
2026-02-09 16:08:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 修改:将列表 dumps 为字符串
|
|
|
|
|
|
if 'generalManual' in data:
|
|
|
|
|
|
material.manual_link = json.dumps(data['generalManual'])
|
|
|
|
|
|
if 'generalImage' in data:
|
|
|
|
|
|
material.product_image = json.dumps(data['generalImage'])
|
2026-01-28 08:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
if 'isEnabled' in data:
|
|
|
|
|
|
material.is_enabled = bool(int(data['isEnabled']))
|
|
|
|
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
return material
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def delete_material(m_id):
|
2026-01-28 17:44:39 +08:00
|
|
|
|
"""
|
|
|
|
|
|
删除基础信息 (带依赖检查)
|
|
|
|
|
|
"""
|
2026-01-28 08:54:11 +08:00
|
|
|
|
try:
|
|
|
|
|
|
material = MaterialBase.query.get(m_id)
|
|
|
|
|
|
if not material:
|
|
|
|
|
|
raise ValueError("数据不存在")
|
|
|
|
|
|
|
2026-01-28 17:44:39 +08:00
|
|
|
|
buy_usage_count = StockBuy.query.filter_by(base_id=m_id).count()
|
|
|
|
|
|
semi_usage_count = StockSemi.query.filter_by(base_id=m_id).count()
|
|
|
|
|
|
total_usage = buy_usage_count + semi_usage_count
|
|
|
|
|
|
|
|
|
|
|
|
if total_usage > 0:
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"无法删除:该基础物料正被使用中。\n"
|
|
|
|
|
|
f"- 采购库存记录: {buy_usage_count} 条\n"
|
|
|
|
|
|
f"- 半成品库存记录: {semi_usage_count} 条\n"
|
|
|
|
|
|
f"请先清理相关库存或仅‘禁用’此条目。"
|
|
|
|
|
|
)
|
2026-01-28 08:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
db.session.delete(material)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
print(f"删除基础信息失败: {e}")
|
|
|
|
|
|
raise e
|