- MaterialBase 映射中新增 warningStatus/warningEnabled/warningRed/warningYellow/warningRedEmails/warningYellowEmails 字段 - 新增 _permits() 函数支持通配符权限匹配 (material_list:* 覆盖所有 material_list:xxx) - 修复 apply_strict_rbac 使用 _permits 替代直接 in 检查 根因: field_permissions.py 的 STOCK_FIELD_RBAC_MAPPING 中没有预警字段映射, apply_strict_rbac 的 Default Deny 规则会删除所有不在映射中的字段,导致前端永远收不到预警数据。
145 lines
8.0 KiB
Python
145 lines
8.0 KiB
Python
"""
|
|
入库模块字段级权限严格映射 (Default Deny)
|
|
|
|
规则:
|
|
- None = 基础字段,始终保留
|
|
- 字符串 = 需要用户拥有该精确权限码,否则移除
|
|
- 不在映射中的字段 → 立即删除 (Default Deny)
|
|
"""
|
|
|
|
STOCK_FIELD_RBAC_MAPPING = {
|
|
"MaterialBase": {
|
|
"id": None, "isEnabled": None, "visibilityLevel": None,
|
|
"name": "material_list:name", "commonName": "material_list:commonName",
|
|
"category": "material_list:category", "type": "material_list:type",
|
|
"spec": "material_list:spec", "unit": "material_list:unit",
|
|
"companyName": "material_list:companyName", "isInspectionRequired": "material_list:isInspectionRequired",
|
|
"generalImage": "material_list:files", "generalManual": "material_list:files",
|
|
"productImageRemark": "material_list:productImageRemark",
|
|
"manualLinkRemark": "material_list:manualLinkRemark",
|
|
"referencePrice": "material_list:referencePrice",
|
|
"inventoryCount": "material_list:inventoryCount",
|
|
"availableCount": "material_list:availableCount",
|
|
"warningStatus": "material_list:view_warning",
|
|
"warningEnabled": "material_list:view_warning",
|
|
"warningRed": "material_list:view_warning",
|
|
"warningYellow": "material_list:view_warning",
|
|
"warningRedEmails": "material_list:view_warning",
|
|
"warningYellowEmails": "material_list:view_warning",
|
|
},
|
|
"StockBuy": {
|
|
"id": None, "request_id": None, "request_no": None,
|
|
"in_quantity": "inbound_buy:in_quantity", "stock_quantity": "inbound_buy:stock_quantity", "available_quantity": "inbound_buy:available_quantity",
|
|
"warehouse_loc": None, "status": None, "global_print_id": None,
|
|
"company_name": "material_list:companyName", "material_name": "material_list:name",
|
|
"spec_model": "material_list:spec", "category": "material_list:category",
|
|
"unit": "material_list:unit", "material_type": "material_list:type",
|
|
"isInspectionRequired": "material_list:isInspectionRequired",
|
|
"sku": "inbound_buy:sku", "inbound_date": "inbound_buy:inbound_date",
|
|
"barcode": "inbound_buy:barcode", "serial_number": "inbound_buy:sn_bn",
|
|
"batch_number": "inbound_buy:sn_bn", "inspection_status": "inbound_buy:inspection_status",
|
|
"unit_price": "inbound_buy:unit_price", "post_tax_unit_price": "inbound_buy:post_tax_unit_price",
|
|
"total_price": "inbound_buy:total_price", "tax_rate": "inbound_buy:tax_rate",
|
|
"currency": "inbound_buy:currency", "exchange_rate": "inbound_buy:exchange_rate",
|
|
"supplier_name": "inbound_buy:supplier_name", "purchaser": "inbound_buy:purchaser",
|
|
"purchaser_email": "inbound_buy:purchaser_email", "source_link": "inbound_buy:source_link",
|
|
"detail_link": "inbound_buy:detail_link", "arrival_photo": "inbound_buy:arrival_photo",
|
|
"inspection_report": "inbound_buy:inspection_report",
|
|
},
|
|
"StockSemi": {
|
|
"id": None, "in_quantity": "inbound_semi:in_quantity", "stock_quantity": "inbound_semi:stock_quantity", "available_quantity": "inbound_semi:available_quantity",
|
|
"warehouse_loc": None, "status": None, "global_print_id": None,
|
|
"company_name": "material_list:companyName", "material_name": "material_list:name",
|
|
"spec_model": "material_list:spec", "category": "material_list:category",
|
|
"unit": "material_list:unit", "material_type": "material_list:type",
|
|
"sku": "inbound_semi:sku", "inbound_date": "inbound_semi:inbound_date",
|
|
"barcode": "inbound_semi:barcode", "serial_number": "inbound_semi:sn_bn",
|
|
"batch_number": "inbound_semi:sn_bn", "bom_code": "inbound_semi:bom_code",
|
|
"bom_version": "inbound_semi:bom_version", "work_order_code": "inbound_semi:work_order_code",
|
|
"raw_material_cost": "inbound_semi:raw_material_cost", "manual_cost": "inbound_semi:manual_cost",
|
|
"unit_total_cost": "inbound_semi:unit_total_cost", "total_price": "inbound_semi:total_price",
|
|
"production_manager": "inbound_semi:production_manager",
|
|
"production_time_range": "inbound_semi:production_time_range",
|
|
"production_start_time": "inbound_semi:production_start_time",
|
|
"production_end_time": "inbound_semi:production_end_time",
|
|
"quality_status": "inbound_semi:quality_status", "quality_report_link": "inbound_semi:quality_report_link",
|
|
"arrival_photo": "inbound_semi:arrival_photo", "remark": "inbound_semi:remark",
|
|
"detail_link": "inbound_semi:detail_link",
|
|
},
|
|
"StockProduct": {
|
|
"id": None, "in_quantity": "inbound_product:in_quantity", "stock_quantity": "inbound_product:stock_quantity", "available_quantity": "inbound_product:available_quantity",
|
|
"warehouse_loc": None, "status": None, "global_print_id": None,
|
|
"company_name": "material_list:companyName", "material_name": "material_list:name",
|
|
"spec_model": "material_list:spec", "category": "material_list:category",
|
|
"unit": "material_list:unit", "material_type": "material_list:type",
|
|
"sku": "inbound_product:sku", "inbound_date": "inbound_product:inbound_date",
|
|
"barcode": "inbound_product:barcode", "serial_number": "inbound_product:serial_number",
|
|
"bom_code": "inbound_product:bom_code", "bom_version": "inbound_product:bom_version",
|
|
"work_order_code": "inbound_product:work_order_code",
|
|
"raw_material_cost": "inbound_product:raw_material_cost", "manual_cost": "inbound_product:manual_cost",
|
|
"unit_total_cost": "inbound_product:unit_total_cost",
|
|
"production_manager": "inbound_product:production_manager",
|
|
"production_time_range": "inbound_product:production_time_range",
|
|
"quality_status": "inbound_product:quality_status",
|
|
"quality_report_link": "inbound_product:quality_report_link",
|
|
"inspection_report_link": "inbound_product:inspection_report_link",
|
|
"sale_price": "inbound_product:sale_price", "order_id": "inbound_product:order_id",
|
|
"product_photo": "inbound_product:product_photo", "remark": "inbound_product:remark",
|
|
"detail_link": "inbound_product:detail_link",
|
|
},
|
|
}
|
|
|
|
|
|
def _is_super_admin(user_permissions: list) -> bool:
|
|
"""判断是否为超级管理员(支持 * 和 module:* 两种通配符)"""
|
|
return '*' in user_permissions or any(p.endswith(':*') for p in user_permissions)
|
|
|
|
|
|
def _permits(perm_code: str, user_permissions: list) -> bool:
|
|
"""检查用户权限列表是否覆盖指定的权限码(支持通配符匹配)"""
|
|
if perm_code is None:
|
|
return True # None = 公开字段
|
|
if perm_code in user_permissions:
|
|
return True # 精确匹配
|
|
# 通配符匹配: material_list:* 覆盖所有 material_list:xxx
|
|
for p in user_permissions:
|
|
if p.endswith(':*') and perm_code.startswith(p[:-2] + ':'):
|
|
return True
|
|
if '*' in user_permissions:
|
|
return True
|
|
return False
|
|
|
|
|
|
def apply_strict_rbac(item_dict: dict, table_name: str, user_permissions: list) -> dict:
|
|
"""
|
|
Default Deny 字段过滤器:
|
|
1. 不在映射中的 key → 立即删除
|
|
2. 在映射中但需要权限且用户没有 → 删除
|
|
3. 在映射中且为 None 或用户有权限 → 保留
|
|
"""
|
|
mapping = STOCK_FIELD_RBAC_MAPPING.get(table_name)
|
|
if not mapping:
|
|
return item_dict # 未知表不做过滤
|
|
|
|
# 超级管理员放行(支持 * / module:* 通配符)
|
|
if _is_super_admin(user_permissions):
|
|
for key in list(item_dict.keys()):
|
|
if key not in mapping:
|
|
del item_dict[key]
|
|
return item_dict
|
|
|
|
for key in list(item_dict.keys()):
|
|
if key not in mapping:
|
|
del item_dict[key] # Default Deny
|
|
else:
|
|
perm_code = mapping[key]
|
|
if not _permits(perm_code, user_permissions):
|
|
if isinstance(item_dict[key], (int, float)):
|
|
item_dict[key] = 0
|
|
elif isinstance(item_dict[key], bool):
|
|
item_dict[key] = False
|
|
else:
|
|
item_dict[key] = None
|
|
|
|
return item_dict
|