from flask import jsonify, request, current_app from flask_jwt_extended import jwt_required, get_jwt_identity from app.models.material import Material from app.api import bp, ns_materials, api from app import db from app.utils.validators import validate_json, validate_decimal from flask_restx import Resource, fields from sqlalchemy import distinct # API 模型定义 material_model = api.model('Material', { 'name': fields.String(required=True, description='Material name'), 'unit': fields.String(required=True, description='Unit of measurement'), 'category': fields.String(required=False, description='Material category'), 'warning_threshold': fields.Float(required=True, description='Warning threshold quantity'), 'processed_name': fields.String(required=False, description='Processed material name'), 'processed_unit': fields.String(required=False, description='Processed material unit'), 'processed_warning_threshold': fields.Float(required=False, description='Processed material warning threshold'), 'conversion_ratio': fields.Float(required=False, description='Conversion ratio from raw to processed material') }) @ns_materials.route('/categories/') class MaterialCategoriesResource(Resource): @api.doc('list_material_categories') @jwt_required() def get(self): """获取所有物料分类列表""" try: # 从数据库中查询所有不同的分类 categories_query = db.session.query(distinct(Material.category)).all() # 提取分类名称,排除空值和None db_categories = [category[0] for category in categories_query if category[0]] # 排序 db_categories.sort() # 按字母顺序排序 # 注释掉强制添加"其他"分类的代码,只显示数据库中存在的分类 # if '其他' not in db_categories: # db_categories.append('其他') # else: # # 如果"其他"分类已存在,将其移到最后 # db_categories.remove('其他') # db_categories.append('其他') current_app.logger.debug(f"Found {len(db_categories)} material categories") return {'data': db_categories} except Exception as e: current_app.logger.error(f'Error fetching material categories: {str(e)}') return {'message': 'Error fetching material categories'}, 500 @ns_materials.route('/') class MaterialResource(Resource): @api.doc('list_materials') @jwt_required() def get(self): """获取所有物料列表""" try: materials = Material.query.all() result = [{ 'id': m.id, 'name': m.name, 'unit': m.unit, 'category': m.category, 'warning_threshold': float(m.warning_threshold), 'processed_name': m.processed_name, 'processed_unit': m.processed_unit, 'processed_warning_threshold': float(m.processed_warning_threshold) if m.processed_warning_threshold else None, 'conversion_ratio': float(m.conversion_ratio) if m.conversion_ratio else None, 'created_at': m.created_at.isoformat() } for m in materials] # 打印调试信息 current_app.logger.debug(f"Found {len(result)} materials") return {'data': result} except Exception as e: current_app.logger.error(f'Error fetching materials: {str(e)}') return {'message': 'Error fetching materials'}, 500 @api.doc('create_material') @api.expect(material_model) @jwt_required() def post(self): """创建新物料""" try: data = request.get_json() # 验证数据 warning_threshold = validate_decimal(data['warning_threshold']) if warning_threshold is None or warning_threshold <= 0: return {'message': 'Invalid warning threshold value'}, 400 # 检查名称是否已存在 if Material.query.filter_by(name=data['name']).first(): return {'message': 'Material name already exists'}, 400 # 处理加工物料相关数据 processed_warning_threshold = None if 'processed_warning_threshold' in data and data['processed_warning_threshold']: processed_warning_threshold = validate_decimal(data['processed_warning_threshold']) if processed_warning_threshold is not None and processed_warning_threshold <= 0: return {'message': 'Invalid processed warning threshold value'}, 400 conversion_ratio = None if 'conversion_ratio' in data and data['conversion_ratio']: conversion_ratio = validate_decimal(data['conversion_ratio']) if conversion_ratio is not None and conversion_ratio <= 0: return {'message': 'Invalid conversion ratio value'}, 400 material = Material( name=data['name'], unit=data['unit'], category=data.get('category', '其他'), warning_threshold=warning_threshold, processed_name=data.get('processed_name'), processed_unit=data.get('processed_unit'), processed_warning_threshold=processed_warning_threshold, conversion_ratio=conversion_ratio ) db.session.add(material) db.session.commit() return jsonify({ 'id': material.id, 'name': material.name, 'unit': material.unit, 'category': material.category, 'warning_threshold': float(material.warning_threshold), 'processed_name': material.processed_name, 'processed_unit': material.processed_unit, 'processed_warning_threshold': float(material.processed_warning_threshold) if material.processed_warning_threshold else None, 'conversion_ratio': float(material.conversion_ratio) if material.conversion_ratio else None, 'created_at': material.created_at.isoformat() }) except Exception as e: current_app.logger.error(f'Error creating material: {str(e)}') db.session.rollback() return {'message': 'Error creating material'}, 500 @ns_materials.route('/') class MaterialDetail(Resource): @api.doc('update_material') @jwt_required() def put(self, id): """更新物料信息""" try: material = Material.query.get_or_404(id) data = request.get_json() if 'name' in data and data['name'] != material.name: if Material.query.filter_by(name=data['name']).first(): return {'message': 'Material name already exists'}, 400 material.name = data['name'] if 'unit' in data: material.unit = data['unit'] if 'category' in data: material.category = data['category'] if 'warning_threshold' in data: warning_threshold = validate_decimal(data['warning_threshold']) if warning_threshold is None or warning_threshold <= 0: return {'message': 'Invalid warning threshold value'}, 400 material.warning_threshold = warning_threshold # 更新物料加工相关字段 if 'processed_name' in data: material.processed_name = data['processed_name'] if 'processed_unit' in data: material.processed_unit = data['processed_unit'] if 'processed_warning_threshold' in data: if data['processed_warning_threshold']: processed_warning_threshold = validate_decimal(data['processed_warning_threshold']) if processed_warning_threshold is not None and processed_warning_threshold <= 0: return {'message': 'Invalid processed warning threshold value'}, 400 material.processed_warning_threshold = processed_warning_threshold else: material.processed_warning_threshold = None if 'conversion_ratio' in data: if data['conversion_ratio']: conversion_ratio = validate_decimal(data['conversion_ratio']) if conversion_ratio is not None and conversion_ratio <= 0: return {'message': 'Invalid conversion ratio value'}, 400 material.conversion_ratio = conversion_ratio else: material.conversion_ratio = None db.session.commit() return jsonify({ 'id': material.id, 'name': material.name, 'unit': material.unit, 'category': material.category, 'warning_threshold': float(material.warning_threshold), 'processed_name': material.processed_name, 'processed_unit': material.processed_unit, 'processed_warning_threshold': float(material.processed_warning_threshold) if material.processed_warning_threshold else None, 'conversion_ratio': float(material.conversion_ratio) if material.conversion_ratio else None, 'created_at': material.created_at.isoformat() }) except Exception as e: current_app.logger.error(f'Error updating material: {str(e)}') db.session.rollback() return {'message': 'Error updating material'}, 500 @api.doc('delete_material') @jwt_required() def delete(self, id): """删除物料""" try: material = Material.query.get_or_404(id) # 检查是否有关联的库存记录,但不阻止删除 has_inventory = len(material.inventories) > 0 if has_inventory: # 如果有库存记录,先删除所有相关的库存记录 for inventory in material.inventories: db.session.delete(inventory) # 删除物料 db.session.delete(material) db.session.commit() return '', 204 except Exception as e: current_app.logger.error(f'Error deleting material: {str(e)}') db.session.rollback() return {'message': 'Error deleting material'}, 500