基础信息读取错误,未修改完成

This commit is contained in:
dxc
2026-01-28 08:54:11 +08:00
parent 9a04f65eb7
commit 7a4ea8acfb
11 changed files with 624 additions and 264 deletions

View File

@ -0,0 +1,96 @@
from flask import Blueprint, request, jsonify
# 修改为这一行,指向 app/services/inbound/base_service.py
from app.services.inbound.base_service import MaterialBaseService
import traceback
# 定义蓝图
# name='inbound_base' 确保全局唯一,防止和其他蓝图重名
inbound_base_bp = Blueprint('inbound_base', __name__)
# ------------------------------------------------------------------
# 1. 获取基础信息列表 (GET)
# 路由: /api/v1/inbound/base/list
# ------------------------------------------------------------------
@inbound_base_bp.route('/list', methods=['GET'])
def get_list():
try:
# 获取分页参数
page = request.args.get('pageNum', 1, type=int)
limit = request.args.get('pageSize', 10, type=int)
# 获取筛选参数
filters = {
"keyword": request.args.get('keyword'),
"category": request.args.get('category'),
"type": request.args.get('type'),
"isEnabled": request.args.get('isEnabled')
}
# 调用 Service 层逻辑
result = MaterialBaseService.get_list(page, limit, filters)
return jsonify({
"code": 200,
"msg": "success",
"data": result
})
except Exception as e:
traceback.print_exc()
return jsonify({"code": 500, "msg": str(e)}), 500
# ------------------------------------------------------------------
# 2. 新增基础信息 (POST)
# 路由: /api/v1/inbound/base/
# ------------------------------------------------------------------
@inbound_base_bp.route('/', methods=['POST'])
def add_material():
try:
data = request.get_json()
if not data:
return jsonify({"code": 400, "msg": "No data provided"}), 400
MaterialBaseService.create_material(data)
return jsonify({"code": 200, "msg": "新增成功"})
except ValueError as ve:
# 捕获业务逻辑验证错误(如名称重复)
return jsonify({"code": 400, "msg": str(ve)}), 400
except Exception as e:
traceback.print_exc()
return jsonify({"code": 500, "msg": "系统错误"}), 500
# ------------------------------------------------------------------
# 3. 修改基础信息 (PUT)
# 路由: /api/v1/inbound/base/
# ------------------------------------------------------------------
@inbound_base_bp.route('/', methods=['PUT'])
def update_material():
try:
data = request.get_json()
if not data or not data.get('id'):
return jsonify({"code": 400, "msg": "ID不能为空"}), 400
MaterialBaseService.update_material(data.get('id'), data)
return jsonify({"code": 200, "msg": "更新成功"})
except Exception as e:
traceback.print_exc()
return jsonify({"code": 500, "msg": str(e)}), 500
# ------------------------------------------------------------------
# 4. 删除基础信息 (DELETE)
# 路由: /api/v1/inbound/base/<id>
# ------------------------------------------------------------------
@inbound_base_bp.route('/<int:id>', methods=['DELETE'])
def delete_material(id):
try:
MaterialBaseService.delete_material(id)
return jsonify({"code": 200, "msg": "删除成功"})
except ValueError as ve:
# 捕获依赖检查错误(如已被库存引用)
return jsonify({"code": 400, "msg": str(ve)}), 400
except Exception as e:
traceback.print_exc()
return jsonify({"code": 500, "msg": str(e)}), 500

View File

@ -1,106 +0,0 @@
from flask import Blueprint, request, jsonify
# 确保这两个引用路径是存在的,如果报错说明文件没建好
try:
from app.services.stock_service import StockService
from app.schemas.stock_schema import stock_buy_schema
except ImportError as e:
# 如果服务还没写好,这里会打印错误,防止整个后端起不来
print(f"❌ 导入服务出错: {e}")
StockService = None
stock_buy_schema = None
stocks_bp = Blueprint('stocks', __name__)
# ------------------------------------------------------------------
# 1. 获取入库列表
# URL: /api/v1/stocks/inbound (GET)
# ------------------------------------------------------------------
@stocks_bp.route('/inbound', methods=['GET'])
def get_inbound_list():
if not StockService:
return jsonify({'code': 500, 'msg': '后端服务未初始化'}), 500
try:
page = request.args.get('page', 1, type=int)
limit = request.args.get('pageSize', 10, type=int)
# 调用 Service 层获取数据
result = StockService.get_list(page, limit)
return jsonify({
'code': 200,
'msg': 'success',
'data': result
})
except Exception as e:
print(f"获取列表报错: {e}")
return jsonify({'code': 500, 'msg': '服务器内部错误'}), 500
# ------------------------------------------------------------------
# 2. 新增入库单
# URL: /api/v1/stocks/inbound (POST)
# ------------------------------------------------------------------
@stocks_bp.route('/inbound', methods=['POST'])
def create_inbound():
if not StockService:
return jsonify({'code': 500, 'msg': '后端服务未初始化'}), 500
json_data = request.get_json()
if not json_data:
return jsonify({'code': 400, 'msg': '没有接收到数据'}), 400
try:
# 1. 参数校验 (Marshmallow Schema)
data = stock_buy_schema.load(json_data)
# 2. 调用业务逻辑
new_stock = StockService.create_inbound(data)
# 3. 返回成功
# 注意:确保 new_stock 对象有 to_dict() 方法,否则这里会报错
return jsonify({
'code': 200,
'msg': '入库成功',
'data': new_stock.to_dict() if hasattr(new_stock, 'to_dict') else str(new_stock)
}), 201
except Exception as e:
# 捕获校验错误或数据库错误
print(f"入库报错: {e}")
return jsonify({'code': 400, 'msg': str(e)}), 400
# ------------------------------------------------------------------
# 3. 更新入库单
# URL: /api/v1/stocks/inbound/<id> (PUT)
# ------------------------------------------------------------------
@stocks_bp.route('/inbound/<int:id>', methods=['PUT'])
def update_inbound(id):
if not StockService:
return jsonify({'code': 500, 'msg': '后端服务未初始化'}), 500
json_data = request.get_json()
try:
StockService.update_inbound(id, json_data)
return jsonify({'code': 200, 'msg': '更新成功'})
except Exception as e:
return jsonify({'code': 400, 'msg': str(e)}), 400
# ------------------------------------------------------------------
# 4. 删除入库单
# URL: /api/v1/stocks/inbound/<id> (DELETE)
# ------------------------------------------------------------------
@stocks_bp.route('/inbound/<int:id>', methods=['DELETE'])
def delete_inbound(id):
if not StockService:
return jsonify({'code': 500, 'msg': '后端服务未初始化'}), 500
try:
StockService.delete_inbound(id)
return jsonify({'code': 200, 'msg': '删除成功'})
except Exception as e:
return jsonify({'code': 400, 'msg': str(e)}), 400

View File

@ -1,32 +1,62 @@
#material.py
# app/models/material.py
from app.extensions import db
from datetime import datetime
class MaterialBase(db.Model):
"""
基础信息表模型
对应数据库表: material_base
"""
__tablename__ = 'material_base'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False) # 名称
category = db.Column(db.String(100)) # 类别
material_type = db.Column(db.String(100)) # 类型
spec_model = db.Column(db.String(255)) # 规格型号
unit = db.Column(db.String(50)) # 计量单位
visibility_level = db.Column(db.Integer, default=0) # 信息可见等级
manual_link = db.Column(db.Text) # 通用说明书
product_image = db.Column(db.Text) # 通用产品图
is_enabled = db.Column(db.Boolean, default=True) # 是否启用
name = db.Column(db.String(255), nullable=False, comment='基础信息名称')
category = db.Column(db.String(100), comment='类别') # 例如: 采购件, 自制件
material_type = db.Column(db.String(100), comment='类型') # 例如: 电子料, 结构件 (对应前端 type)
spec_model = db.Column(db.String(255), comment='规格型号') # (对应前端 spec)
unit = db.Column(db.String(50), comment='计量单位')
# 根据你提供的代码,可见等级设为 Integer默认为 0
visibility_level = db.Column(db.Integer, default=0, comment='信息可见等级')
manual_link = db.Column(db.Text, comment='通用说明书链接') # (对应前端 generalManual)
product_image = db.Column(db.Text, comment='通用产品图链接') # (对应前端 generalImage)
is_enabled = db.Column(db.Boolean, default=True, comment='是否启用')
# 时间字段(建议加上,用于排序或记录)
create_time = db.Column(db.DateTime, default=datetime.utcnow)
update_time = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 【核心关联】
# 这里定义了反向关系lazy='dynamic' 允许我们后续做 count() 查询
# cascade='all, delete-orphan' 并不是在这里用的,因为我们是手动控制逻辑
# 关联采购库存表lazy='dynamic' 允许使用 .count() 查询方法
stock_buys = db.relationship('StockBuy', back_populates='material', lazy='dynamic')
def to_dict(self):
"""
序列化方法将模型转换为字典供API返回JSON使用
此处进行了字段名的映射,以适配 list.vue 前端的 prop 属性
"""
return {
'id': self.id,
'name': self.name,
'category': self.category,
'material_type': self.material_type,
'spec_model': self.spec_model,
'unit': self.unit
# --- 字段映射区域 (后端字段 -> 前端字段) ---
'type': self.material_type, # 前端 prop="type"
'spec': self.spec_model, # 前端 prop="spec"
'unit': self.unit,
# 转为驼峰命名,适配前端习惯
'visibilityLevel': self.visibility_level,
'generalManual': self.manual_link,
'generalImage': self.product_image,
# Element Plus Switch 组件通常接受 1/0 或 true/false
# 这里转为 1/0 方便前端 el-switch :active-value="1" :inactive-value="0"
'isEnabled': 1 if self.is_enabled else 0,
# 补充时间信息
'createTime': self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time else None
}

View File

@ -0,0 +1,139 @@
from app.extensions import db
from app.models.material import MaterialBase
from app.models.stock import StockBuy # 需要引入库存表做删除时的依赖检查
from sqlalchemy import or_
import traceback
class MaterialBaseService:
@staticmethod
def get_list(page, limit, filters=None):
"""
获取基础信息列表
:param page: 页码
:param limit: 每页条数
:param filters: 筛选条件字典 {keyword, category, type, isEnabled}
"""
try:
query = MaterialBase.query
if filters:
# 1. 关键词模糊搜索 (名称 或 规格型号)
if filters.get('keyword'):
kw = f"%{filters['keyword']}%"
query = query.filter(or_(
MaterialBase.name.like(kw),
MaterialBase.spec_model.like(kw)
))
# 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("名称和规格型号不能为空")
# 1. 查重 (名称+规格型号 唯一)
exist = MaterialBase.query.filter_by(
name=data['name'],
spec_model=data['spec']
).first()
if exist:
raise ValueError(f"已存在相同名称和规格的数据 (ID: {exist.id})")
# 2. 创建对象 (注意前端驼峰 -> 后端下划线映射)
new_material = MaterialBase(
name=data['name'],
spec_model=data['spec'], # 映射
category=data.get('category'),
material_type=data.get('type'), # 映射
unit=data.get('unit'),
visibility_level=data.get('visibilityLevel'), # 映射
manual_link=data.get('generalManual'), # 映射
product_image=data.get('generalImage'), # 映射
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("数据不存在")
# 更新字段 (仅更新传入的字段)
if 'name' in data: material.name = data['name']
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']
if 'generalManual' in data: material.manual_link = data['generalManual']
if 'generalImage' in data: material.product_image = data['generalImage']
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):
"""删除基础信息 (带依赖检查)"""
try:
material = MaterialBase.query.get(m_id)
if not material:
raise ValueError("数据不存在")
# 1. 依赖检查:如果该基础信息已经在库存表(StockBuy)中使用,禁止物理删除
# 这里假设 StockBuy 表有一个外键或字段指向 MaterialBase (e.g., base_id)
usage_count = StockBuy.query.filter_by(base_id=m_id).count()
if usage_count > 0:
raise ValueError(f"无法删除:该基础信息已被 {usage_count} 条库存记录引用,请先清理库存或仅禁用此条目。")
# 2. 执行删除
db.session.delete(material)
db.session.commit()
return True
except Exception as e:
db.session.rollback()
print(f"删除基础信息失败: {e}")
raise e

View File

@ -1,119 +0,0 @@
from app.extensions import db
from app.models.stock import StockBuy
from app.models.material import MaterialBase
from sqlalchemy.exc import SQLAlchemyError
class StockService:
@staticmethod
def create_inbound(data):
"""
处理入库逻辑:
1. 根据 SKU 查找物料。
2. 如果没找到,创建新物料 (MaterialBase)。
3. 创建入库单 (StockBuy)。
"""
try:
sku = data.get('sku_code')
material_id = data.get('material_id')
# --- 第一步:确定 material_id ---
# 如果前端没传 ID或者传了但我们想二次确认都通过 SKU 查一遍
existing_material = MaterialBase.query.filter_by(sku_code=sku).first()
if existing_material:
# 场景 A: 物料已存在 -> 直接使用其 ID
material_id = existing_material.id
else:
# 场景 B: 物料不存在 -> 自动创建新物料
if not data.get('material_name'):
raise ValueError(f"SKU [{sku}] 是新物料,必须填写【物料名称】才能入库。")
new_material = MaterialBase(
sku_code=sku,
name=data.get('material_name'),
spec_model=data.get('spec_model'),
unit=data.get('unit'),
category=data.get('category')
)
db.session.add(new_material)
db.session.flush() # 关键:将对象刷入暂存区,以获取自增的 ID
material_id = new_material.id
# --- 第二步:创建入库单 ---
qty = data.get('qty_inbound')
price = data.get('price_unit', 0)
new_stock = StockBuy(
material_id=material_id,
inbound_date=data.get('inbound_date'),
batch_no=data.get('batch_no'),
warehouse_loc=data.get('warehouse_loc'),
supplier_name=data.get('supplier_name'),
# 数量逻辑:初始时,当前量 = 可用量 = 入库量
qty_inbound=qty,
qty_current=qty,
qty_available=qty,
# 财务逻辑
price_unit=price,
price_total=float(qty) * float(price) if qty else 0
)
db.session.add(new_stock)
db.session.commit() # 统一提交事务
return new_stock
except SQLAlchemyError as e:
db.session.rollback() # 数据库报错回滚
raise e
except ValueError as e:
db.session.rollback() # 业务报错回滚
raise e
@staticmethod
def get_list(page, per_page):
"""获取分页列表"""
pagination = StockBuy.query.order_by(StockBuy.inbound_date.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return {
'items': [item.to_dict() for item in pagination.items],
'total': pagination.total,
'pages': pagination.pages,
'current_page': pagination.page
}
@staticmethod
def update_inbound(stock_id, data):
"""更新入库单信息 (通常不允许改物料本身,只改入库相关)"""
stock = StockBuy.query.get_or_404(stock_id)
if 'warehouse_loc' in data: stock.warehouse_loc = data['warehouse_loc']
if 'supplier_name' in data: stock.supplier_name = data['supplier_name']
if 'batch_no' in data: stock.batch_no = data['batch_no']
if 'price_unit' in data: stock.price_unit = data['price_unit']
# 如果修改了数量,需要级联更新当前库存
if 'qty_inbound' in data:
old_qty = float(stock.qty_inbound)
new_qty = float(data['qty_inbound'])
diff = new_qty - old_qty
stock.qty_inbound = new_qty
stock.qty_current = float(stock.qty_current) + diff
stock.qty_available = float(stock.qty_available) + diff
db.session.commit()
return stock
@staticmethod
def delete_inbound(stock_id):
"""删除入库单"""
stock = StockBuy.query.get_or_404(stock_id)
db.session.delete(stock)
db.session.commit()

View File

@ -5,4 +5,8 @@ app = create_app()
if __name__ == '__main__':
# debug=True 修改代码后会自动重启
print("\n====== 当前所有注册路由 ======")
for rule in app.url_map.iter_rules():
print(f"{rule} -> {rule.endpoint}")
print("==============================\n")
app.run(host='0.0.0.0', port=5000, debug=True)