save_draft / get_draft_detail / publish_draft 三处此前都不带 BOM 级备注, 暂存后再恢复、或草稿直接发布,备注都会丢。 同时给 /save 的权限清洗表补上顶级 remark(与子件级 remark 共用 bom_manage:remark 权限码)。 注意:运行时真正生效的草稿入口是 bom.py:521 的 /draft/save。 app/api/v1/bom_draft.py 里的 bom_draft_bp 从未在 app/__init__.py 注册, 是死代码(两份都调同一个 BomDraftService,本次两处都改以保持同步)。 实测(临时 BOM,验证后已清理): 草稿暂存带备注 → 读回 '草稿BOM级备注-测试' 草稿 → 发布 → 正式表 读回 '草稿备注-发布后应保留'
580 lines
22 KiB
Python
580 lines
22 KiB
Python
from flask import Blueprint, request, jsonify, current_app
|
||
from sqlalchemy import or_
|
||
from app.services.bom_service import BomService, _cache_delete
|
||
from app.services.bom_draft_service import BomDraftService
|
||
from app.models.base import MaterialBase
|
||
from app.models.bom import BomTable
|
||
from app.extensions import db
|
||
from flask_jwt_extended import jwt_required, get_jwt
|
||
from app.utils.decorators import permission_required
|
||
from app.services.auth_service import AuthService
|
||
|
||
bom_bp = Blueprint('bom', __name__)
|
||
|
||
|
||
# ==============================================================================
|
||
# 辅助函数:获取当前用户的完整权限列表(基于角色查询)
|
||
# ==============================================================================
|
||
def get_current_user_permissions():
|
||
"""
|
||
返回当前用户拥有的所有权限码列表(包括菜单和元素)
|
||
此函数根据角色查询数据库得到权限。
|
||
"""
|
||
claims = get_jwt()
|
||
user_role = claims.get('role')
|
||
user_company = claims.get('company_name', '')
|
||
if not user_role:
|
||
return []
|
||
# 超级管理员返回所有字段权限 (忽略大小写)
|
||
if user_role.upper() == 'SUPER_ADMIN':
|
||
return ['bom_manage:*']
|
||
perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company)
|
||
# 合并菜单和元素权限
|
||
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
|
||
return perms
|
||
|
||
|
||
def filter_item_by_permissions(item_dict, user_permissions):
|
||
"""
|
||
根据用户权限过滤 item 字典,无权限的字段值置为 None
|
||
"""
|
||
# 字段名到权限码的映射(与前端 permissionMap 保持一致)
|
||
field_to_perm = {
|
||
'bom_no': 'bom_manage:bom_no',
|
||
'parent_name': 'bom_manage:parent_name',
|
||
'parent_spec': 'bom_manage:parent_spec',
|
||
'version': 'bom_manage:version',
|
||
'is_enabled': 'bom_manage:status',
|
||
'child_count': 'bom_manage:child_count',
|
||
}
|
||
# 如果用户是超级管理员且有 'bom_manage:*',则不过滤
|
||
if 'bom_manage:*' in user_permissions:
|
||
return item_dict
|
||
for field, perm_code in field_to_perm.items():
|
||
if field in item_dict and perm_code not in user_permissions:
|
||
item_dict[field] = None
|
||
return item_dict
|
||
|
||
|
||
# ==================== 新版 BOM 接口(基于 bom_no) ====================
|
||
|
||
@bom_bp.route('/list', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_bom_list():
|
||
"""获取 BOM 列表,支持 keyword、active_only、category 过滤和分页"""
|
||
try:
|
||
keyword = request.args.get('keyword', '').strip()
|
||
active_only = request.args.get('active_only', 'false').lower() == 'true'
|
||
status = request.args.get('status', '').strip() or None
|
||
category = request.args.get('category', '').strip() or None
|
||
page = request.args.get('page', 1, type=int)
|
||
limit = request.args.get('pageSize', 15, type=int)
|
||
|
||
data = BomService.get_bom_list(
|
||
keyword=keyword, active_only=active_only,
|
||
category=category, page=page, limit=limit, status=status
|
||
)
|
||
# 字段级脱敏(data 现在是 {items, total, pages, current_page} 字典)
|
||
user_permissions = get_current_user_permissions()
|
||
if data.get('items'):
|
||
data['items'] = [filter_item_by_permissions(item, user_permissions) for item in data['items']]
|
||
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 分组摘要接口 (GET /api/v1/bom/summary)
|
||
# 极轻量查询:仅 GROUP BY category + COUNT(DISTINCT bom_no+version)
|
||
# ==============================================================================
|
||
@bom_bp.route('/summary', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_bom_summary():
|
||
try:
|
||
keyword = request.args.get('keyword', '').strip() or None
|
||
status = request.args.get('status', '').strip() or None
|
||
data = BomService.get_bom_summary(keyword=keyword, status=status)
|
||
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 独立启停(仅更新状态,不触发整表保存) ====================
|
||
@bom_bp.route('/status', methods=['POST'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage:operation')
|
||
def update_bom_status():
|
||
"""仅切换某 BOM 版本(整组)的启用/停用状态。"""
|
||
try:
|
||
data = request.get_json() or {}
|
||
bom_no = data.get('bom_no')
|
||
version = data.get('version')
|
||
is_enabled = data.get('is_enabled')
|
||
if not bom_no or not version:
|
||
return jsonify({'code': 400, 'msg': 'bom_no 与 version 不能为空'}), 400
|
||
if not isinstance(is_enabled, bool):
|
||
return jsonify({'code': 400, 'msg': 'is_enabled 必须为布尔值'}), 400
|
||
BomService.update_enabled(bom_no, version, is_enabled)
|
||
return jsonify({'code': 200, 'msg': '更新成功'})
|
||
except ValueError as e:
|
||
return jsonify({'code': 400, 'msg': str(e)}), 400
|
||
except Exception as e:
|
||
current_app.logger.error(f'更新BOM状态失败: {str(e)}')
|
||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||
|
||
|
||
# ==================== BOM 归档/取消归档(仍启用可编辑,但不作为其它 BOM 子件引用候选) ====================
|
||
@bom_bp.route('/archive', methods=['POST'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage:operation')
|
||
def update_bom_archive():
|
||
"""切换某 BOM 版本(整组)的归档状态。"""
|
||
try:
|
||
data = request.get_json() or {}
|
||
bom_no = data.get('bom_no')
|
||
version = data.get('version')
|
||
is_archived = data.get('is_archived')
|
||
if not bom_no or not version:
|
||
return jsonify({'code': 400, 'msg': 'bom_no 与 version 不能为空'}), 400
|
||
if not isinstance(is_archived, bool):
|
||
return jsonify({'code': 400, 'msg': 'is_archived 必须为布尔值'}), 400
|
||
BomService.update_archived(bom_no, version, is_archived)
|
||
return jsonify({'code': 200, 'msg': '更新成功'})
|
||
except ValueError as e:
|
||
return jsonify({'code': 400, 'msg': str(e)}), 400
|
||
except Exception as e:
|
||
current_app.logger.error(f'更新BOM归档状态失败: {str(e)}')
|
||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||
|
||
|
||
@bom_bp.route('/detail/<path:bom_no>', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_bom_detail(bom_no):
|
||
"""
|
||
根据 BOM 编号获取配方详情
|
||
Query参数: ?version=V1.0 (如果不传则取最新)
|
||
"""
|
||
try:
|
||
version = request.args.get('version')
|
||
data = BomService.get_bom_detail(bom_no, version=version)
|
||
if not data:
|
||
return jsonify({'code': 404, 'msg': 'BOM 不存在'}), 404
|
||
# 字段级脱敏
|
||
user_permissions = get_current_user_permissions()
|
||
data = filter_item_by_permissions(data, user_permissions)
|
||
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('/save', methods=['POST'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage:operation')
|
||
def save_bom():
|
||
"""保存或更新 BOM 配方(支持自定义 bom_no 和 多版本)"""
|
||
try:
|
||
req_data = request.get_json()
|
||
# 数据清洗:移除用户没有权限的字段
|
||
user_permissions = get_current_user_permissions()
|
||
# 超级管理员不过滤
|
||
if 'bom_manage:*' not in user_permissions:
|
||
# 字段名到权限码的映射
|
||
field_to_perm = {
|
||
'parent_id': 'bom_manage:parent_id',
|
||
'version': 'bom_manage:version',
|
||
'is_enabled': 'bom_manage:status',
|
||
'bom_no': 'bom_manage:bom_no',
|
||
# ★ 顶级 remark = BOM 级备注,与子件级 remark 共用同一权限码
|
||
'remark': 'bom_manage:remark',
|
||
}
|
||
# 清洗顶级字段
|
||
for field in list(req_data.keys()):
|
||
perm_code = field_to_perm.get(field)
|
||
if perm_code and perm_code not in user_permissions:
|
||
req_data.pop(field, None)
|
||
# 清洗 children 中的字段
|
||
if 'children' in req_data and isinstance(req_data['children'], list):
|
||
for child in req_data['children']:
|
||
# 子件字段映射
|
||
child_field_to_perm = {
|
||
'child_id': 'bom_manage:child_id',
|
||
'dosage': 'bom_manage:dosage',
|
||
'remark': 'bom_manage:remark',
|
||
}
|
||
for field in list(child.keys()):
|
||
perm_code = child_field_to_perm.get(field)
|
||
if perm_code and perm_code not in user_permissions:
|
||
child.pop(field, None)
|
||
|
||
# 必需字段校验
|
||
if 'parent_id' not in req_data or 'children' not in req_data:
|
||
return jsonify({'code': 400, 'msg': '缺少 parent_id 或 children 字段'}), 400
|
||
|
||
# 校验 bom_no 不能为空
|
||
if 'bom_no' in req_data and not req_data['bom_no']:
|
||
return jsonify({'code': 400, 'msg': 'BOM编号不能为空'}), 400
|
||
|
||
bom_no = BomService.save_bom(req_data)
|
||
return jsonify({
|
||
'code': 200,
|
||
'msg': '保存成功',
|
||
'data': {'bom_no': bom_no}
|
||
})
|
||
except ValueError as e:
|
||
return jsonify({'code': 400, 'msg': str(e)}), 400
|
||
except Exception as e:
|
||
current_app.logger.error(f'保存BOM失败: {str(e)}')
|
||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||
|
||
|
||
@bom_bp.route('/stock/<path:bom_no>', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_bom_with_stock_by_no(bom_no):
|
||
"""根据 BOM 编号 (和可选 version) 获取配方详情及库存信息"""
|
||
try:
|
||
version = request.args.get('version')
|
||
data = BomService.get_bom_with_stock_by_bom_no(bom_no, version=version)
|
||
if not data:
|
||
return jsonify({'code': 404, 'msg': 'BOM 不存在'}), 404
|
||
# 字段级脱敏
|
||
user_permissions = get_current_user_permissions()
|
||
data = filter_item_by_permissions(data, user_permissions)
|
||
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接口 ====================
|
||
|
||
@bom_bp.route('/<path:bom_no>', methods=['DELETE'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage:operation')
|
||
def delete_bom(bom_no):
|
||
"""
|
||
根据 BOM 编号删除
|
||
Query参数: ?version=V1.0 (如果不传,删除该编号下所有版本)
|
||
"""
|
||
try:
|
||
version = request.args.get('version')
|
||
query = BomTable.query.filter_by(bom_no=bom_no)
|
||
|
||
if version:
|
||
query = query.filter_by(version=version)
|
||
|
||
# 【核心修复】:使用 .all() 查出该 BOM 版本下的所有子件记录
|
||
records = query.all()
|
||
|
||
if not records:
|
||
return jsonify({'code': 404, 'msg': 'BOM 不存在'}), 404
|
||
|
||
# 循环删除所有关联记录(逐个 delete 可触发 SQLAlchemy 监听器记录审计日志)
|
||
for rec in records:
|
||
db.session.delete(rec)
|
||
|
||
db.session.commit()
|
||
|
||
# ===== 删除成功后立刻清除缓存(Cache Invalidation) =====
|
||
_cache_delete(bom_no, version)
|
||
current_app.logger.info(f"[BOM Cache] delete_bom → 缓存已失效 bom_no={bom_no} version={version}")
|
||
|
||
return jsonify({
|
||
'code': 200,
|
||
'msg': '删除成功',
|
||
'bom_no': bom_no
|
||
})
|
||
except Exception as e:
|
||
current_app.logger.error(f'删除BOM失败: {str(e)}')
|
||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||
|
||
|
||
# ==================== 兼容旧接口 ====================
|
||
|
||
@bom_bp.route('/<int:parent_id>', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_bom(parent_id):
|
||
try:
|
||
data = BomService.get_bom_with_stock(parent_id)
|
||
# 字段级脱敏
|
||
user_permissions = get_current_user_permissions()
|
||
data = filter_item_by_permissions(data, user_permissions)
|
||
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()
|
||
@permission_required('bom_manage:operation')
|
||
def save_bom_legacy():
|
||
try:
|
||
req_data = request.get_json()
|
||
# 数据清洗:移除用户没有权限的字段
|
||
user_permissions = get_current_user_permissions()
|
||
# 超级管理员不过滤
|
||
if 'bom_manage:*' not in user_permissions:
|
||
# 字段名到权限码的映射
|
||
field_to_perm = {
|
||
'parent_id': 'bom_manage:parent_id',
|
||
'version': 'bom_manage:version',
|
||
'is_enabled': 'bom_manage:status',
|
||
'bom_no': 'bom_manage:bom_no',
|
||
}
|
||
# 清洗顶级字段
|
||
for field in list(req_data.keys()):
|
||
perm_code = field_to_perm.get(field)
|
||
if perm_code and perm_code not in user_permissions:
|
||
req_data.pop(field, None)
|
||
# 清洗 children 中的字段
|
||
if 'children' in req_data and isinstance(req_data['children'], list):
|
||
for child in req_data['children']:
|
||
# 子件字段映射
|
||
child_field_to_perm = {
|
||
'child_id': 'bom_manage:child_id',
|
||
'dosage': 'bom_manage:dosage',
|
||
'remark': 'bom_manage:remark',
|
||
}
|
||
for field in list(child.keys()):
|
||
perm_code = child_field_to_perm.get(field)
|
||
if perm_code and perm_code not in user_permissions:
|
||
child.pop(field, None)
|
||
|
||
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 ValueError as e:
|
||
return jsonify({'code': 400, 'msg': str(e)}), 400
|
||
except Exception as e:
|
||
current_app.logger.error(f'保存BOM失败: {str(e)}')
|
||
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||
|
||
|
||
@bom_bp.route('/base/list', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_material_base_list():
|
||
"""获取基础物料列表,支持分页和关键字搜索,用于前端下拉框"""
|
||
try:
|
||
# 获取分页和搜索参数
|
||
page = int(request.args.get('page', 1))
|
||
limit = int(request.args.get('limit', 20))
|
||
keyword = request.args.get('keyword', '').strip()
|
||
|
||
# 构建查询条件
|
||
query = MaterialBase.query.filter_by(is_enabled=True)
|
||
|
||
# ★ 行级公司隔离:普通用户只能选本公司的物料作为 BOM 子件(超管/跨域不受限)
|
||
from app.utils.decorators import get_current_company_filter
|
||
company_limit = get_current_company_filter()
|
||
if company_limit is not None:
|
||
query = query.filter(MaterialBase.company_name == company_limit)
|
||
|
||
# 添加关键字模糊搜索
|
||
if keyword:
|
||
query = query.filter(
|
||
or_(
|
||
MaterialBase.name.ilike(f"%{keyword}%"),
|
||
MaterialBase.spec_model.ilike(f"%{keyword}%")
|
||
)
|
||
)
|
||
|
||
# 执行分页查询
|
||
pagination = query.order_by(MaterialBase.id.desc()).paginate(
|
||
page=page, per_page=limit, error_out=False
|
||
)
|
||
|
||
# 构建返回数据 — ★ Fail-Closed: 剥离 referencePrice
|
||
items = []
|
||
for item in pagination.items:
|
||
d = item.to_dict()
|
||
d.pop('referencePrice', None)
|
||
items.append(d)
|
||
data = {
|
||
'list': items,
|
||
'total': pagination.total
|
||
}
|
||
|
||
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
|
||
|
||
|
||
@bom_bp.route('/self-bom-versions', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_child_bom_versions():
|
||
"""
|
||
获取某物料作为父件时的"启用配方版本清单"(bom_no, version)。
|
||
前端在编辑/新建 BOM、选中自制件子件后调用,用于生成必选版本下拉。
|
||
Query参数: child_id (必填, 物料ID)
|
||
返回 data: [{bom_no, version}, ...];空数组 = 该物料非自制件(无现行 BOM),无需选版本。
|
||
"""
|
||
try:
|
||
child_id = request.args.get('child_id', type=int)
|
||
if not child_id:
|
||
return jsonify({'code': 400, 'msg': 'child_id 不能为空'}), 400
|
||
data = BomService.get_child_bom_versions(child_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('/parents', methods=['GET'])
|
||
@jwt_required()
|
||
@permission_required('bom_manage')
|
||
def get_bom_parents():
|
||
"""获取所有已定义BOM的父件物料列表(兼容旧版)"""
|
||
try:
|
||
subq = db.session.query(BomTable.parent_id).distinct().subquery()
|
||
parents = MaterialBase.query.join(subq, MaterialBase.id == subq.c.parent_id).all()
|
||
data = [item.to_dict() for item in parents]
|
||
# 字段级脱敏 (如果需要)
|
||
user_permissions = get_current_user_permissions()
|
||
data = [filter_item_by_permissions(item, user_permissions) for item in data]
|
||
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('/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
|
||
|
||
|
||
# ==================== BOM 草稿接口 ====================
|
||
|
||
@bom_bp.route('/draft/save', methods=['POST'])
|
||
@jwt_required()
|
||
def save_draft():
|
||
"""暂存草稿"""
|
||
data = request.get_json()
|
||
bom_no = data.get('bom_no')
|
||
version = data.get('version', 'V1.0')
|
||
parent_id = data.get('parent_id')
|
||
children = data.get('children', [])
|
||
|
||
if not bom_no:
|
||
return jsonify({'code': 400, 'msg': 'bom_no 不能为空'}), 400
|
||
if not parent_id:
|
||
return jsonify({'code': 400, 'msg': 'parent_id 不能为空'}), 400
|
||
|
||
# ★ 这是运行时真正生效的草稿入口(bom_draft.py 的 bom_draft_bp 未在
|
||
# app/__init__.py 注册,是死代码),BOM 级备注必须在这里透传
|
||
bom_draft_no = BomDraftService.save_draft(
|
||
bom_no, version, parent_id, children,
|
||
bom_remark=data.get('remark', '') or ''
|
||
)
|
||
return jsonify({'code': 200, 'msg': '草稿暂存成功', 'data': {'bom_no': bom_draft_no}})
|
||
|
||
|
||
@bom_bp.route('/draft/detail', methods=['GET'])
|
||
@jwt_required()
|
||
def get_draft_detail():
|
||
"""读取草稿详情"""
|
||
bom_no = request.args.get('bom_no')
|
||
version = request.args.get('version', 'V1.0')
|
||
|
||
if not bom_no:
|
||
return jsonify({'code': 400, 'msg': 'bom_no 不能为空'}), 400
|
||
|
||
draft = BomDraftService.get_draft_detail(bom_no, version)
|
||
|
||
# 【核心修改】:查不到草稿是正常现象,返回 HTTP 200 即可
|
||
if draft is None:
|
||
return jsonify({'code': 200, 'msg': '无草稿', 'data': None}), 200
|
||
|
||
return jsonify({'code': 200, 'msg': '查询成功', 'data': draft})
|
||
|
||
|
||
@bom_bp.route('/draft/publish', methods=['POST'])
|
||
@jwt_required()
|
||
def publish_draft():
|
||
"""发布草稿为正式 BOM"""
|
||
data = request.get_json()
|
||
bom_no = data.get('bom_no')
|
||
version = data.get('version', 'V1.0')
|
||
|
||
if not bom_no:
|
||
return jsonify({'code': 400, 'msg': 'bom_no 不能为空'}), 400
|
||
|
||
try:
|
||
bom_draft_no = BomDraftService.publish_draft(bom_no, version)
|
||
return jsonify({'code': 200, 'msg': 'BOM 发布成功', 'data': {'bom_no': bom_draft_no}})
|
||
except ValueError as e:
|
||
return jsonify({'code': 400, 'msg': str(e)}), 400
|