From 40abb53721fd41a13317a28e1e28fdd743df81ae Mon Sep 17 00:00:00 2001 From: dxc Date: Mon, 9 Feb 2026 15:07:54 +0800 Subject: [PATCH] (no commit message provided) Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) --- inventory-backend/app/api/v1/__init__.py | 7 +++ inventory-backend/app/api/v1/bom.py | 37 +++++++++++ inventory-backend/app/models/bom.py | 17 +++++ inventory-backend/app/services/bom_service.py | 63 +++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 inventory-backend/app/api/v1/bom.py create mode 100644 inventory-backend/app/models/bom.py create mode 100644 inventory-backend/app/services/bom_service.py diff --git a/inventory-backend/app/api/v1/__init__.py b/inventory-backend/app/api/v1/__init__.py index e69de29..0313ea0 100644 --- a/inventory-backend/app/api/v1/__init__.py +++ b/inventory-backend/app/api/v1/__init__.py @@ -0,0 +1,7 @@ +from flask import Blueprint +from .inbound import inbound_bp +from .bom import bom_bp + +v1_bp = Blueprint('v1', __name__) +v1_bp.register_blueprint(inbound_bp, url_prefix='/inbound') +v1_bp.register_blueprint(bom_bp, url_prefix='/bom') diff --git a/inventory-backend/app/api/v1/bom.py b/inventory-backend/app/api/v1/bom.py new file mode 100644 index 0000000..068224a --- /dev/null +++ b/inventory-backend/app/api/v1/bom.py @@ -0,0 +1,37 @@ +from flask import Blueprint, request, jsonify, current_app +from app.services.bom_service import BomService +from flask_jwt_extended import jwt_required + +bom_bp = Blueprint('bom', __name__) + +@bom_bp.route('/', methods=['GET']) +@jwt_required() +def get_bom(parent_id): + try: + data = BomService.get_bom_with_stock(parent_id) + return jsonify({ + 'code': 200, + 'msg': 'success', + 'data': data + }) + except Exception as e: + current_app.logger.error(f'获取BOM失败: {str(e)}') + return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500 + +@bom_bp.route('', methods=['POST']) +@jwt_required() +def save_bom(): + try: + req_data = request.get_json() + parent_id = req_data.get('parent_id') + child_list = req_data.get('children', []) + if not parent_id or not isinstance(child_list, list): + return jsonify({'code': 400, 'msg': '参数错误'}), 400 + BomService.create_or_update_bom(parent_id, child_list) + return jsonify({ + 'code': 200, + 'msg': '保存成功' + }) + except Exception as e: + current_app.logger.error(f'保存BOM失败: {str(e)}') + return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500 diff --git a/inventory-backend/app/models/bom.py b/inventory-backend/app/models/bom.py new file mode 100644 index 0000000..55ff046 --- /dev/null +++ b/inventory-backend/app/models/bom.py @@ -0,0 +1,17 @@ +from app.extensions import db + +class BomTable(db.Model): + __tablename__ = 'bom_table' + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False) + child_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False) + bom_no = db.Column(db.String(100), comment='BOM编号') + version = db.Column(db.String(50), comment='版本') + dosage = db.Column(db.Numeric(19, 4), comment='用量') + loss_rate = db.Column(db.Numeric(5, 2), comment='损耗率%') + remark = db.Column(db.Text, comment='备注') + + # relationships + parent = db.relationship('MaterialBase', foreign_keys=[parent_id], backref='bom_parents') + child = db.relationship('MaterialBase', foreign_keys=[child_id], backref='bom_children') diff --git a/inventory-backend/app/services/bom_service.py b/inventory-backend/app/services/bom_service.py new file mode 100644 index 0000000..f1e762f --- /dev/null +++ b/inventory-backend/app/services/bom_service.py @@ -0,0 +1,63 @@ +from app.extensions import db +from app.models.bom import BomTable +from app.models.base import MaterialBase +from app.models.inbound.buy import StockBuy +from sqlalchemy import func + +class BomService: + @staticmethod + def create_or_update_bom(parent_id, child_list): + """ + 保存/更新父件的BOM子件关系 + child_list: [{"child_id": int, "dosage": float, "remark": str}, ...] + """ + # 删除该父件原有的BOM记录 + BomTable.query.filter_by(parent_id=parent_id).delete() + # 插入新的 + for item in child_list: + bom = BomTable( + parent_id=parent_id, + child_id=item['child_id'], + dosage=item.get('dosage', 0), + remark=item.get('remark', '') + ) + db.session.add(bom) + db.session.commit() + return True + + @staticmethod + def get_bom_with_stock(parent_id): + """ + 查询父件的BOM结构及库存信息 + """ + bom_items = db.session.query( + BomTable, + MaterialBase.name.label('child_name') + ).join( + MaterialBase, BomTable.child_id == MaterialBase.id + ).filter( + BomTable.parent_id == parent_id + ).all() + + result = [] + for bom, child_name in bom_items: + # 查询该子件在 StockBuy 中的可用库存总量 + stock_qty = db.session.query( + func.coalesce(func.sum(StockBuy.available_quantity), 0) + ).filter( + StockBuy.base_id == bom.child_id + ).scalar() or 0 + + # 计算最大可生产数量 + dosage = float(bom.dosage) if bom.dosage else 0 + max_producible = int(stock_qty // dosage) if dosage > 0 else 0 + + result.append({ + 'child_id': bom.child_id, + 'child_name': child_name, + 'dosage': dosage, + 'current_stock': float(stock_qty), + 'max_producible': max_producible, + 'remark': bom.remark or '' + }) + return result