Files
KCGL/inventory-backend/app/api/v1/outbound.py

230 lines
9.2 KiB
Python
Raw Normal View History

from flask import Blueprint, request, jsonify
from app.services.outbound_service import OutboundService
from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt
from app.utils.decorators import permission_required
from app.services.auth_service import AuthService
import traceback
outbound_bp = Blueprint('outbound', __name__, url_prefix='/outbound')
# ==============================================================================
# 辅助函数:获取当前用户的完整权限列表(基于角色查询)
# ==============================================================================
def get_current_user_permissions():
"""
返回当前用户拥有的所有权限码列表(包括菜单和元素)
此函数根据角色查询数据库得到权限。
"""
from flask_jwt_extended import get_jwt
from app.services.auth_service import AuthService
claims = get_jwt()
user_role = claims.get('role')
if not user_role:
return []
# 超级管理员返回所有字段权限 (忽略大小写)
if user_role.upper() == 'SUPER_ADMIN':
return ['outbound_list:*']
perm_dict = AuthService.get_user_permissions(user_role)
# 合并菜单和元素权限
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 = {
'outbound_no': 'outbound_list:outbound_no',
'outbound_time': 'outbound_list:outbound_time',
'outbound_type': 'outbound_list:outbound_type',
'total_amount': 'outbound_list:total_amount',
'consumer_name': 'outbound_list:consumer_name',
'operator_name': 'outbound_list:operator_name',
'remark': 'outbound_list:remark',
'signature_path': 'outbound_list:signature_path',
# 明细字段
'sku': 'outbound_list:sku',
'name': 'outbound_list:name',
'material_type': 'outbound_list:material_type',
'category': 'outbound_list:category',
'spec_model': 'outbound_list:spec_model',
'quantity': 'outbound_list:quantity',
'unit_price': 'outbound_list:unit_price',
'subtotal': 'outbound_list:subtotal',
}
# 如果用户是超级管理员且有 'outbound_list:*',则不过滤
if 'outbound_list:*' 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
# 如果 item_dict 中包含 items 列表,递归处理每个子项
if 'items' in item_dict and isinstance(item_dict['items'], list):
for sub_item in item_dict['items']:
filter_item_by_permissions(sub_item, user_permissions)
return item_dict
# --------------------------------------------------------
# 1. 扫码查询库存接口 (关联三个库存表)
# GET /api/v1/outbound/scan?barcode=...
# --------------------------------------------------------
@outbound_bp.route('/scan', methods=['GET'])
@jwt_required()
@permission_required('outbound_selection')
def scan_barcode():
barcode = request.args.get('barcode')
if not barcode:
return jsonify({'code': 400, 'msg': '请提供条码'}), 400
try:
# 调用 Service 层去三个表中查找 (Service已更新,会返回价格)
result = OutboundService.get_stock_by_barcode(barcode)
if result:
return jsonify({
'code': 200,
'msg': '扫描成功',
'data': result
})
else:
return jsonify({
'code': 404,
'msg': '未找到对应的库存记录,请确认条码是否正确'
}), 404
except Exception as e:
traceback.print_exc()
return jsonify({'code': 500, 'msg': f'扫描查询出错: {str(e)}'}), 500
# --------------------------------------------------------
# 2. 提交出库单接口 (批量)
# POST /api/v1/outbound
# --------------------------------------------------------
@outbound_bp.route('', methods=['POST'])
@jwt_required()
def create_outbound():
# 权限检查:需要 outbound_create:operation 或 outbound_selection:operation 之一
claims = get_jwt()
user_role = claims.get('role')
if not user_role:
return jsonify({'code': 403, 'msg': '未授权'}), 403
# 超级管理员直接放行
if user_role.upper() != 'SUPER_ADMIN':
perm_dict = AuthService.get_user_permissions(user_role)
perms = perm_dict.get('menus', []) + perm_dict.get('elements', [])
if ('outbound_create:operation' not in perms) and ('outbound_selection:operation' not in perms):
return jsonify({'code': 403, 'msg': '权限不足'}), 403
data = request.get_json()
if not data:
return jsonify({'code': 400, 'msg': '无有效数据'}), 400
# 获取当前登录用户名 (JWT identity)
current_user_name = get_jwt_identity()
if not current_user_name:
current_user_name = 'Unknown'
# 获取最终的操作员名称
final_operator = data.get('operator_name')
if not final_operator:
final_operator = current_user_name
# 必填校验 (针对整个单据)
# items 必须是列表且不为空,consumer_name 和 signature_path 必填
if 'items' not in data or not data['items']:
return jsonify({'code': 400, 'msg': '出库商品列表不能为空'}), 400
if not data.get('consumer_name') or not data.get('signature_path'):
return jsonify({'code': 400, 'msg': '领用人及签名信息缺失'}), 400
# 数据清洗:移除用户没有权限的字段
user_permissions = get_current_user_permissions()
# 超级管理员不过滤
if 'outbound_list:*' not in user_permissions:
# 字段名到权限码的映射(与前端 permissionMap 保持一致)
field_to_perm = {
'outbound_no': 'outbound_list:outbound_no',
'outbound_time': 'outbound_list:outbound_time',
'outbound_type': 'outbound_list:outbound_type',
'total_amount': 'outbound_list:total_amount',
'consumer_name': 'outbound_list:consumer_name',
'operator_name': 'outbound_list:operator_name',
'remark': 'outbound_list:remark',
'signature_path': 'outbound_list:signature_path',
# 明细字段
'sku': 'outbound_list:sku',
'name': 'outbound_list:name',
'material_type': 'outbound_list:material_type',
'category': 'outbound_list:category',
'spec_model': 'outbound_list:spec_model',
'quantity': 'outbound_list:quantity',
'unit_price': 'outbound_list:unit_price',
'price': 'outbound_list:unit_price', # 兼容 price 字段
'subtotal': 'outbound_list:subtotal',
}
# 清洗顶层字段
for field in list(data.keys()):
perm_code = field_to_perm.get(field)
if perm_code and perm_code not in user_permissions:
data.pop(field, None)
# 清洗 items 中的每个商品字段
if 'items' in data and isinstance(data['items'], list):
for item in data['items']:
for field in list(item.keys()):
perm_code = field_to_perm.get(field)
if perm_code and perm_code not in user_permissions:
item.pop(field, None)
try:
# ★ [修改] 调用批量创建服务
outbound_no = OutboundService.create_outbound_batch(data, operator_name=final_operator)
return jsonify({
'code': 200,
'msg': '出库成功',
'data': {'outbound_no': outbound_no}
})
except ValueError as e:
# 业务逻辑错误 (如库存不足)
return jsonify({'code': 400, 'msg': str(e)}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}), 500
# --------------------------------------------------------
# 3. 获取出库记录列表 (分组展示)
# GET /api/v1/outbound
# --------------------------------------------------------
@outbound_bp.route('', methods=['GET'])
@jwt_required()
@permission_required('outbound_list')
def get_outbound_list():
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
keyword = request.args.get('keyword', '')
# 如果前端传了日期范围,可以解析处理,这里暂略
# ★ [修改] 调用分组查询服务
result = OutboundService.get_grouped_list(page, limit, keyword)
# 字段级脱敏
user_permissions = get_current_user_permissions()
if result.get('items'):
result['items'] = [filter_item_by_permissions(item, user_permissions) for item in result['items']]
return jsonify({
'code': 200,
'msg': '获取成功',
'data': result
})
except Exception as e:
traceback.print_exc()
return jsonify({'code': 500, 'msg': str(e)}), 500