新增 /cascade-inventory 级联库存缺口查询接口,供 AI 调用 BOM 出库缺口分析
This commit is contained in:
@ -382,3 +382,41 @@ def get_bom_parents():
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'获取BOM父件列表失败: {str(e)}')
|
||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||
|
||||
|
||||
@bom_bp.route('/cascade-inventory', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('bom_manage')
|
||||
def get_cascade_inventory():
|
||||
"""
|
||||
根据 BOM 编号和订单数量,计算所有子件的级联库存缺口(供 AI 调用)
|
||||
Query参数:
|
||||
- bom_no: BOM编号(必填)
|
||||
- order_qty: 订单需求量(必填,数值)
|
||||
"""
|
||||
try:
|
||||
bom_no = request.args.get('bom_no', '').strip()
|
||||
order_qty_str = request.args.get('order_qty', '').strip()
|
||||
|
||||
if not bom_no:
|
||||
return jsonify({'code': 400, 'msg': 'bom_no 不能为空'}), 400
|
||||
if not order_qty_str:
|
||||
return jsonify({'code': 400, 'msg': 'order_qty 不能为空'}), 400
|
||||
|
||||
try:
|
||||
order_qty = float(order_qty_str)
|
||||
except ValueError:
|
||||
return jsonify({'code': 400, 'msg': 'order_qty 必须为有效数字'}), 400
|
||||
|
||||
data = BomService.calculate_cascade_inventory(bom_no, order_qty)
|
||||
if data is None:
|
||||
return jsonify({'code': 404, 'msg': 'BOM 不存在'}), 404
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'msg': 'success',
|
||||
'data': data
|
||||
})
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'级联库存计算失败: {str(e)}')
|
||||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||
|
||||
@ -2,6 +2,8 @@ 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 app.models.inbound.semi import StockSemi
|
||||
from app.models.inbound.product import StockProduct
|
||||
from sqlalchemy import func, distinct, or_, case
|
||||
from collections import defaultdict
|
||||
import uuid
|
||||
@ -457,4 +459,69 @@ class BomService:
|
||||
bom_no = BomService.get_bom_no_by_parent(parent_id)
|
||||
if not bom_no: return []
|
||||
detail = BomService.get_bom_with_stock_by_bom_no(bom_no)
|
||||
return detail['children'] if detail else []
|
||||
return detail['children'] if detail else []
|
||||
|
||||
@staticmethod
|
||||
def calculate_cascade_inventory(bom_no, order_qty):
|
||||
"""
|
||||
根据 bom_no 和订单数量,计算所有子件的级联库存缺口。
|
||||
返回结构供 AI 消费,每个子件包含:parent_name / spec / name / level_type /
|
||||
need_qty / available_stock / suggested_qty / gap
|
||||
若 BOM 不存在返回 None。
|
||||
"""
|
||||
# 1. 获取 BOM 明细
|
||||
detail = BomService.get_bom_detail(bom_no)
|
||||
if not detail or not detail.get('children'):
|
||||
return None
|
||||
|
||||
parent_name = detail.get('parent_name', '')
|
||||
|
||||
# 2. 提取所有子件 ID,查询采购库存(stock_buy)
|
||||
child_ids = [child['child_id'] for child in detail['children']]
|
||||
|
||||
buy_stats = db.session.query(
|
||||
StockBuy.base_id,
|
||||
func.coalesce(func.sum(StockBuy.available_quantity), 0).label('total_qty')
|
||||
).filter(
|
||||
StockBuy.base_id.in_(child_ids)
|
||||
).group_by(StockBuy.base_id).all()
|
||||
|
||||
buy_map = {stat.base_id: float(stat.total_qty) for stat in buy_stats}
|
||||
|
||||
# 3. 提取所有子件的基础物料信息(名称/规格/类型)
|
||||
materials = db.session.query(
|
||||
MaterialBase.id,
|
||||
MaterialBase.name,
|
||||
MaterialBase.spec_model
|
||||
).filter(MaterialBase.id.in_(child_ids)).all()
|
||||
|
||||
mat_map = {
|
||||
m.id: {'name': m.name or '', 'spec': m.spec_model or ''}
|
||||
for m in materials
|
||||
}
|
||||
|
||||
# 4. 遍历子件,计算每个子件的缺口数据
|
||||
results = []
|
||||
for child in detail['children']:
|
||||
child_id = child['child_id']
|
||||
dosage = float(child.get('dosage') or 0)
|
||||
need_qty = dosage * order_qty
|
||||
|
||||
available_stock = buy_map.get(child_id, 0)
|
||||
suggested_qty = max(0.0, min(need_qty, available_stock))
|
||||
gap = available_stock - need_qty
|
||||
|
||||
mat_info = mat_map.get(child_id, {'name': '', 'spec': ''})
|
||||
|
||||
results.append({
|
||||
'parent_name': parent_name,
|
||||
'spec': mat_info['spec'],
|
||||
'name': mat_info['name'],
|
||||
'level_type': 'child',
|
||||
'need_qty': round(need_qty, 4),
|
||||
'available_stock': round(available_stock, 4),
|
||||
'suggested_qty': round(suggested_qty, 4),
|
||||
'gap': round(gap, 4),
|
||||
})
|
||||
|
||||
return results
|
||||
Reference in New Issue
Block a user