721 lines
32 KiB
Python
721 lines
32 KiB
Python
"""字段级权限:字段清单单一来源 + 权限解析 + 后端强制过滤。
|
||
|
||
设计目标(对应「字段级权限」优化):
|
||
1. 字段清单单一来源:FIELD_REGISTRY 定义每个页面的字段(key=中文显示名,与前端列定义、
|
||
现有 user_permissions 存储保持一致),sensitive 标记敏感字段。
|
||
2. 权限存储复用现有表结构,无需迁移:
|
||
- 角色级字段权限:role_operation_permissions,operation = 'field:<字段>:view' / 'field:<字段>:edit'
|
||
- 用户级字段权限:user_permissions,operation 同上
|
||
3. 敏感字段默认隐藏:敏感字段未显式授权时,默认拒绝查看/编辑(安全优先)。
|
||
4. 后端强制过滤:filter_rows / filter_row 按有效字段权限剔除无权查看的字段。
|
||
|
||
权限解析优先级:管理员全权限 → 用户级明确值 → 角色级明确值 → 敏感字段默认拒绝 / 非敏感默认继承页面权限。
|
||
"""
|
||
from services.db import query
|
||
from services.permission_model import ensure_position_permissions, user_position_code
|
||
|
||
# ─── 字段清单单一来源 ───
|
||
# key 统一使用中文显示名(与前端列定义一致),sensitive=True 表示敏感字段(默认隐藏,需显式授权)。
|
||
FIELD_REGISTRY = {
|
||
# 计划预测(与 PLAN_COLUMNS 对齐)
|
||
'planning': [
|
||
{'key': '产品类型', 'sensitive': False},
|
||
{'key': '产品种类', 'sensitive': False},
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '批注', 'sensitive': False},
|
||
{'key': '新旧品', 'sensitive': False},
|
||
{'key': '下单清单', 'sensitive': False},
|
||
{'key': '产品分级', 'sensitive': False},
|
||
{'key': '在途进度', 'sensitive': False},
|
||
{'key': '昨日销量', 'sensitive': False},
|
||
{'key': '云仓昨日销量', 'sensitive': False},
|
||
{'key': '外仓昨日销量', 'sensitive': False},
|
||
{'key': '销售趋势', 'sensitive': False},
|
||
{'key': '总库存', 'sensitive': False},
|
||
{'key': '库存', 'sensitive': False},
|
||
{'key': '总在途库存', 'sensitive': False},
|
||
{'key': '云仓库存', 'sensitive': False},
|
||
{'key': '外仓库存', 'sensitive': False},
|
||
{'key': '加工厂库存', 'sensitive': False},
|
||
{'key': '云仓可分配库存', 'sensitive': False},
|
||
{'key': '外仓可分配库存', 'sensitive': False},
|
||
{'key': '是否调拨', 'sensitive': False},
|
||
{'key': '生产在途', 'sensitive': False},
|
||
{'key': '采购在途', 'sensitive': False},
|
||
{'key': '日销量预测', 'sensitive': False},
|
||
{'key': '云仓日销量预测', 'sensitive': False},
|
||
{'key': '外仓日销量预测', 'sensitive': False},
|
||
{'key': '建议下单数量', 'sensitive': False},
|
||
{'key': '连续建议天数', 'sensitive': False},
|
||
{'key': '实际下单数量', 'sensitive': False},
|
||
{'key': '正式下单数量', 'sensitive': False},
|
||
{'key': '库存健康指数', 'sensitive': False},
|
||
{'key': '风险把控', 'sensitive': False},
|
||
{'key': '供应商名称', 'sensitive': False},
|
||
{'key': '采购跟单', 'sensitive': False},
|
||
{'key': '采购计划', 'sensitive': False},
|
||
{'key': '开发专员', 'sensitive': False},
|
||
{'key': '归属平台', 'sensitive': False},
|
||
{'key': '运营专员', 'sensitive': False},
|
||
{'key': '成本价', 'sensitive': True},
|
||
{'key': '3天平均销量', 'sensitive': False},
|
||
{'key': '云仓3天平均销量', 'sensitive': False},
|
||
{'key': '外仓3天平均销量', 'sensitive': False},
|
||
{'key': '7天平均销量', 'sensitive': False},
|
||
{'key': '云仓7天平均销量', 'sensitive': False},
|
||
{'key': '外仓7天平均销量', 'sensitive': False},
|
||
{'key': '14天平均销量', 'sensitive': False},
|
||
{'key': '云仓14天平均销量', 'sensitive': False},
|
||
{'key': '外仓14天平均销量', 'sensitive': False},
|
||
{'key': '库存可支撑天数', 'sensitive': False},
|
||
{'key': '现货采购在途', 'sensitive': False},
|
||
{'key': '加工厂库存支撑', 'sensitive': False},
|
||
{'key': '定制品在途', 'sensitive': False},
|
||
{'key': '总支撑天数', 'sensitive': False},
|
||
{'key': '云仓支撑天数', 'sensitive': False},
|
||
{'key': '外仓支撑天数', 'sensitive': False},
|
||
{'key': '建议回货数量', 'sensitive': False},
|
||
{'key': '产品规格', 'sensitive': False},
|
||
{'key': '30天平均销量', 'sensitive': False},
|
||
{'key': '60天平均销量', 'sensitive': False},
|
||
{'key': '生产货期', 'sensitive': False},
|
||
{'key': '物流时间', 'sensitive': False},
|
||
{'key': '最低起订量', 'sensitive': False},
|
||
{'key': '创建时间', 'sensitive': False},
|
||
{'key': '是否新品', 'sensitive': False},
|
||
{'key': '一级类目', 'sensitive': False},
|
||
{'key': '二级类目', 'sensitive': False},
|
||
{'key': '三级类目', 'sensitive': False},
|
||
],
|
||
# 数据概览
|
||
'data_summary': [
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '店铺', 'sensitive': False},
|
||
{'key': '总数量', 'sensitive': False},
|
||
{'key': '总成本', 'sensitive': True},
|
||
{'key': '日期', 'sensitive': False},
|
||
{'key': '数据来源', 'sensitive': False},
|
||
],
|
||
# 数据明细
|
||
'data_detail': [
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '店铺', 'sensitive': False},
|
||
{'key': '一级类目', 'sensitive': False},
|
||
{'key': '二级类目', 'sensitive': False},
|
||
{'key': '三级类目', 'sensitive': False},
|
||
{'key': '货品数量', 'sensitive': False},
|
||
{'key': '货品成本', 'sensitive': True},
|
||
{'key': '日期', 'sensitive': False},
|
||
{'key': '数据来源', 'sensitive': False},
|
||
],
|
||
# 库存-成品
|
||
'inventory_finished': [
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '可用库存', 'sensitive': False},
|
||
{'key': '生产在途', 'sensitive': False},
|
||
{'key': '采购在途', 'sensitive': False},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '库存日期', 'sensitive': False},
|
||
{'key': '采购跟单', 'sensitive': False},
|
||
{'key': '采购计划', 'sensitive': False},
|
||
],
|
||
# 库存-包材
|
||
'inventory_packaging': [
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '可用库存', 'sensitive': False},
|
||
{'key': '生产在途', 'sensitive': False},
|
||
{'key': '采购在途', 'sensitive': False},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '库存日期', 'sensitive': False},
|
||
],
|
||
# 采购待下单
|
||
'purchase_pending': [
|
||
{'key': '产品类型', 'sensitive': False},
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '确认下单', 'sensitive': False},
|
||
{'key': '产品分级', 'sensitive': False},
|
||
{'key': '供应商名称', 'sensitive': False},
|
||
{'key': '建议下单数量', 'sensitive': False},
|
||
{'key': '正式下单数量', 'sensitive': False},
|
||
{'key': '下单备注', 'sensitive': False},
|
||
{'key': '货期', 'sensitive': False},
|
||
{'key': '物流', 'sensitive': False},
|
||
{'key': '总支撑天数', 'sensitive': False},
|
||
{'key': '平台', 'sensitive': False},
|
||
{'key': '运营', 'sensitive': False},
|
||
{'key': '审核价格', 'sensitive': True},
|
||
{'key': '下单人员', 'sensitive': False},
|
||
{'key': '下单时间', 'sensitive': False},
|
||
{'key': '采购跟单', 'sensitive': False},
|
||
{'key': '采购计划', 'sensitive': False},
|
||
{'key': '加入时间', 'sensitive': False},
|
||
],
|
||
# 采购已下单
|
||
'purchase_backup': [
|
||
{'key': '产品类型', 'sensitive': False},
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '下单时间', 'sensitive': False},
|
||
{'key': '下单人', 'sensitive': False},
|
||
{'key': '生产单号', 'sensitive': False},
|
||
{'key': '建议下单数量', 'sensitive': False},
|
||
{'key': '正式下单数量', 'sensitive': False},
|
||
{'key': '产品分级', 'sensitive': False},
|
||
{'key': '供应商名称', 'sensitive': False},
|
||
{'key': '下单备注', 'sensitive': False},
|
||
{'key': '货期', 'sensitive': False},
|
||
{'key': '物流', 'sensitive': False},
|
||
{'key': '总支撑天数', 'sensitive': False},
|
||
{'key': '采购跟单', 'sensitive': False},
|
||
{'key': '采购计划', 'sensitive': False},
|
||
{'key': '平台', 'sensitive': False},
|
||
{'key': '运营', 'sensitive': False},
|
||
{'key': '审核价格', 'sensitive': True},
|
||
],
|
||
# 国内订单
|
||
'order_domestic': [
|
||
{'key': '日期', 'sensitive': False},
|
||
{'key': '计划下单时间', 'sensitive': False},
|
||
{'key': '新旧品', 'sensitive': False},
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '货品类型', 'sensitive': False},
|
||
{'key': '下单备注', 'sensitive': False},
|
||
{'key': '单价', 'sensitive': True},
|
||
{'key': '审核价格', 'sensitive': True},
|
||
{'key': '数量', 'sensitive': False},
|
||
{'key': '金额', 'sensitive': True},
|
||
{'key': '供货商', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '金蝶采购单', 'sensitive': False},
|
||
{'key': '采购员', 'sensitive': False},
|
||
{'key': '下单人', 'sensitive': False},
|
||
{'key': '进度', 'sensitive': False},
|
||
{'key': '旺店通单号', 'sensitive': False},
|
||
{'key': '采购开发', 'sensitive': False},
|
||
{'key': '预计到货日期', 'sensitive': False},
|
||
{'key': '金蝶入库', 'sensitive': False},
|
||
],
|
||
# 海外订单
|
||
'order_overseas': [
|
||
{'key': '产品编码', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '货品类型', 'sensitive': False},
|
||
{'key': '日期', 'sensitive': False},
|
||
{'key': '计划下单时间', 'sensitive': False},
|
||
{'key': '单价', 'sensitive': True},
|
||
{'key': '审核价格', 'sensitive': True},
|
||
{'key': '数量', 'sensitive': False},
|
||
{'key': '金额', 'sensitive': True},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '新旧品', 'sensitive': False},
|
||
{'key': '下单备注', 'sensitive': False},
|
||
{'key': '采购员', 'sensitive': False},
|
||
{'key': '下单人', 'sensitive': False},
|
||
{'key': '进度', 'sensitive': False},
|
||
{'key': '旺店通单号', 'sensitive': False},
|
||
{'key': '采购开发', 'sensitive': False},
|
||
{'key': '预计到货日期', 'sensitive': False},
|
||
],
|
||
# 包材订单
|
||
'order_packaging': [
|
||
{'key': '日期', 'sensitive': False},
|
||
{'key': '计划下单时间', 'sensitive': False},
|
||
{'key': '产品编码', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '货品类型', 'sensitive': False},
|
||
{'key': '单价', 'sensitive': True},
|
||
{'key': '审核价格', 'sensitive': True},
|
||
{'key': '数量', 'sensitive': False},
|
||
{'key': '金额', 'sensitive': True},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '成品供应商', 'sensitive': False},
|
||
{'key': '仓库', 'sensitive': False},
|
||
{'key': '新旧品', 'sensitive': False},
|
||
{'key': '采购员', 'sensitive': False},
|
||
{'key': '下单人', 'sensitive': False},
|
||
{'key': '进度', 'sensitive': False},
|
||
{'key': '下单备注', 'sensitive': False},
|
||
{'key': '采购开发', 'sensitive': False},
|
||
{'key': '旺店通单号', 'sensitive': False},
|
||
],
|
||
# 成品订单
|
||
'order_finished': [
|
||
{'key': '品类', 'sensitive': False},
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '货品类型', 'sensitive': False},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '可用库存', 'sensitive': False},
|
||
{'key': '千羽仓库存', 'sensitive': False},
|
||
{'key': '昨日销量', 'sensitive': False},
|
||
{'key': '3天均销', 'sensitive': False},
|
||
{'key': '7天均销', 'sensitive': False},
|
||
{'key': '15天均销', 'sensitive': False},
|
||
{'key': '支撑天数', 'sensitive': False},
|
||
{'key': '采购总数量', 'sensitive': False},
|
||
{'key': '生产在途', 'sensitive': False},
|
||
{'key': '采购在途', 'sensitive': False},
|
||
{'key': '产品进度', 'sensitive': False},
|
||
{'key': '加工厂库存', 'sensitive': False},
|
||
{'key': '跟单小组', 'sensitive': False},
|
||
{'key': '采购计划', 'sensitive': False},
|
||
{'key': '备注', 'sensitive': False},
|
||
],
|
||
# 产品基础信息
|
||
'products': [
|
||
{'key': '货品编号', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '货品类型', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '供应商编码', 'sensitive': False},
|
||
{'key': '供应商名称', 'sensitive': False},
|
||
{'key': '成本价', 'sensitive': True},
|
||
{'key': '货期', 'sensitive': False},
|
||
{'key': '物流时间', 'sensitive': False},
|
||
{'key': '最低起订量', 'sensitive': False},
|
||
{'key': '采购跟单', 'sensitive': False},
|
||
{'key': '采购计划', 'sensitive': False},
|
||
{'key': '平台', 'sensitive': False},
|
||
{'key': '运营', 'sensitive': False},
|
||
{'key': '开发专员', 'sensitive': False},
|
||
{'key': '创建时间', 'sensitive': False},
|
||
{'key': '导入日期', 'sensitive': False},
|
||
],
|
||
# 新品管理
|
||
'product_new': [
|
||
{'key': '产品编码', 'sensitive': False},
|
||
{'key': '产品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '条形码', 'sensitive': False},
|
||
{'key': '容量', 'sensitive': False},
|
||
{'key': '成品备注', 'sensitive': False},
|
||
{'key': '产品总成本', 'sensitive': True},
|
||
{'key': '产品总货款', 'sensitive': True},
|
||
{'key': '是否含运', 'sensitive': False},
|
||
{'key': '产品开发', 'sensitive': False},
|
||
{'key': '包材开发', 'sensitive': False},
|
||
{'key': '设计师', 'sensitive': False},
|
||
{'key': '负责运营', 'sensitive': False},
|
||
],
|
||
# 新品包材管理(独立于新品成品管理;字段来自 new_product_bom)
|
||
'product_packaging': [
|
||
{'key': '成品编码', 'sensitive': False},
|
||
{'key': '成品名称', 'sensitive': False},
|
||
{'key': '物料编码', 'sensitive': False},
|
||
{'key': '物料名称', 'sensitive': False},
|
||
{'key': '用量', 'sensitive': False},
|
||
{'key': '供应商编码', 'sensitive': False},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '货期/天', 'sensitive': False},
|
||
{'key': '税点', 'sensitive': True},
|
||
{'key': '是否含税', 'sensitive': False},
|
||
{'key': '单价/元', 'sensitive': True},
|
||
{'key': '数量', 'sensitive': False},
|
||
{'key': '货款/元', 'sensitive': True},
|
||
{'key': '结款方式', 'sensitive': False},
|
||
{'key': '是否可下单', 'sensitive': False},
|
||
{'key': '下单备注', 'sensitive': False},
|
||
{'key': '通用物料编码', 'sensitive': False},
|
||
{'key': '通用物料名称', 'sensitive': False},
|
||
{'key': '下单日期', 'sensitive': False},
|
||
],
|
||
# BOM
|
||
'bom': [
|
||
{'key': '成品编号', 'sensitive': False},
|
||
{'key': '成品名称', 'sensitive': False},
|
||
{'key': '物料编号', 'sensitive': False},
|
||
{'key': '物料名称', 'sensitive': False},
|
||
{'key': '用量', 'sensitive': False},
|
||
{'key': '供应商', 'sensitive': False},
|
||
{'key': '成本价', 'sensitive': True},
|
||
{'key': '创建时间', 'sensitive': False},
|
||
],
|
||
# 仓库
|
||
'warehouse': [
|
||
{'key': '仓库编号', 'sensitive': False},
|
||
{'key': '仓库名称', 'sensitive': False},
|
||
{'key': '仓库类型', 'sensitive': False},
|
||
{'key': '地址', 'sensitive': False},
|
||
{'key': '联系人', 'sensitive': False},
|
||
{'key': '联系电话', 'sensitive': False},
|
||
{'key': '创建时间', 'sensitive': False},
|
||
],
|
||
# 供应商
|
||
'suppliers': [
|
||
{'key': '供应商编码', 'sensitive': False},
|
||
{'key': '供应商名称', 'sensitive': False},
|
||
{'key': '联系人', 'sensitive': False},
|
||
{'key': '联系电话', 'sensitive': False},
|
||
{'key': '地址', 'sensitive': False},
|
||
{'key': '合作状态', 'sensitive': False},
|
||
{'key': '创建时间', 'sensitive': False},
|
||
],
|
||
# 现货销售
|
||
'spot_sales': [
|
||
{'key': '商家编码', 'sensitive': False},
|
||
{'key': '货品名称', 'sensitive': False},
|
||
{'key': '品牌', 'sensitive': False},
|
||
{'key': '首批入库时间', 'sensitive': False},
|
||
{'key': '首批成本价', 'sensitive': True},
|
||
],
|
||
# AI 分析:金额类数据源单独控制(敏感,默认不开放)
|
||
'ai_analysis': [
|
||
{'key': '库存成本汇总', 'sensitive': True},
|
||
{'key': '商品库存金额', 'sensitive': True},
|
||
{'key': '品牌汇总', 'sensitive': True},
|
||
],
|
||
}
|
||
|
||
# 运行页面ID → 标准权限页面ID 回退映射
|
||
_FIELD_REGISTRY_ALIASES = {
|
||
'purchase': 'purchase_pending',
|
||
'purchase_ready': 'purchase_backup',
|
||
'orders_domestic': 'order_domestic',
|
||
'orders_overseas': 'order_overseas',
|
||
'orders_packaging': 'order_packaging',
|
||
'orders_finished': 'order_finished',
|
||
}
|
||
|
||
# 标准页面ID → 存储/历史别名集合(与 routes/auth.py 的 PERMISSION_PAGE_ALIASES 保持一致)
|
||
_PAGE_ALIASES = {
|
||
'purchase_pending': ['purchase_pending', 'purchase'],
|
||
'purchase_backup': ['purchase_backup', 'purchase_ready', 'purchase'],
|
||
'order_domestic': ['order_domestic', 'orders_domestic', 'orders'],
|
||
'order_overseas': ['order_overseas', 'orders_overseas', 'orders'],
|
||
'order_packaging': ['order_packaging', 'orders_packaging', 'orders'],
|
||
'order_finished': ['order_finished', 'orders_finished', 'orders'],
|
||
'data_summary': ['data_summary', 'data'],
|
||
'data_detail': ['data_detail', 'data'],
|
||
'inventory_finished': ['inventory_finished', 'inventory'],
|
||
'inventory_packaging': ['inventory_packaging', 'inventory'],
|
||
'product_new': ['product_new'],
|
||
'product_packaging': ['product_packaging'],
|
||
'spot_sales': ['spot_sales', 'spot'],
|
||
}
|
||
|
||
# 标准页面ID → 前端运行页面ID(注入前端时使用)
|
||
_RUNTIME_PAGE = {
|
||
'purchase_pending': 'purchase',
|
||
'purchase_backup': 'purchase_ready',
|
||
'order_domestic': 'orders_domestic',
|
||
'order_overseas': 'orders_overseas',
|
||
'order_packaging': 'orders_packaging',
|
||
'order_finished': 'orders_finished',
|
||
}
|
||
|
||
# 敏感字段的响应 key 映射(后端过滤时按实际返回字段名剔除)。
|
||
# 未列出的字段默认按 key(中文显示名)匹配响应字段。
|
||
FIELD_RESP_KEYS = {
|
||
# 采购待下单/已下单接口使用英文键名,统一映射到权限字段中文名。
|
||
('purchase_pending', '产品类型'): ['product_type'],
|
||
('purchase_pending', '货品编号'): ['product_code'],
|
||
('purchase_pending', '货品名称'): ['product_name'],
|
||
('purchase_pending', '产品分级'): ['product_grade'],
|
||
('purchase_pending', '供应商名称'): ['supplier_name'],
|
||
('purchase_pending', '建议下单数量'): ['suggested_qty'],
|
||
('purchase_pending', '正式下单数量'): ['formal_order_qty'],
|
||
('purchase_pending', '下单备注'): ['remark'],
|
||
('purchase_pending', '货期'): ['lead_time'],
|
||
('purchase_pending', '物流'): ['logistics_time'],
|
||
('purchase_pending', '总支撑天数'): ['total_support_days'],
|
||
('purchase_pending', '平台'): ['platform'],
|
||
('purchase_pending', '运营'): ['operator'],
|
||
('purchase_pending', '审核价格'): ['audit_price'],
|
||
('purchase_pending', '下单人员'): ['created_by'],
|
||
('purchase_pending', '下单时间'): ['created_at'],
|
||
('purchase_pending', '采购跟单'): ['procurement_follower'],
|
||
('purchase_pending', '采购计划'): ['procurement_planner'],
|
||
('purchase_pending', '加入时间'): ['created_at'],
|
||
('purchase_backup', '产品类型'): ['product_type'],
|
||
('purchase_backup', '货品编号'): ['product_code'],
|
||
('purchase_backup', '货品名称'): ['product_name'],
|
||
('purchase_backup', '供应商名称'): ['supplier_name'],
|
||
('purchase_backup', '建议下单数量'): ['suggested_qty'],
|
||
('purchase_backup', '正式下单数量'): ['formal_order_qty'],
|
||
('purchase_backup', '下单备注'): ['remark'],
|
||
('purchase_backup', '货期'): ['lead_time'],
|
||
('purchase_backup', '物流'): ['logistics_time'],
|
||
('purchase_backup', '总支撑天数'): ['total_support_days'],
|
||
('purchase_backup', '平台'): ['platform'],
|
||
('purchase_backup', '运营'): ['operator'],
|
||
('purchase_backup', '审核价格'): ['audit_price'],
|
||
('purchase_backup', '下单人员'): ['created_by'],
|
||
('purchase_backup', '下单时间'): ['ordered_at'],
|
||
('purchase_backup', '采购跟单'): ['procurement_follower'],
|
||
('purchase_backup', '采购计划'): ['procurement_planner'],
|
||
('purchase_backup', '生产单号'): ['production_order_no'],
|
||
# 包材订单接口字段与权限字段名称不同。
|
||
('order_packaging', '产品编码'): ['编码'],
|
||
('order_packaging', '货品名称'): ['名称'],
|
||
('order_packaging', '金额'): ['合计'],
|
||
('order_packaging', '供应商'): ['供应商'],
|
||
('order_packaging', '旺店通单号'): ['旺店通编号'],
|
||
('product_packaging', '成品编码'): ['finished_product_code'],
|
||
('product_packaging', '成品名称'): ['finished_product_name'],
|
||
('product_packaging', '物料编码'): ['material_code'],
|
||
('product_packaging', '物料名称'): ['material_name'],
|
||
('product_packaging', '供应商编码'): ['supplier_code'],
|
||
('product_packaging', '供应商'): ['supplier'],
|
||
('product_packaging', '货期/天'): ['lead_time'],
|
||
('product_packaging', '税点'): ['tax_rate'],
|
||
('product_packaging', '是否含税'): ['tax_included'],
|
||
('product_packaging', '单价/元'): ['unit_price'],
|
||
('product_packaging', '数量'): ['quantity'],
|
||
('product_packaging', '货款/元'): ['amount'],
|
||
('product_packaging', '结款方式'): ['payment_method'],
|
||
('product_packaging', '是否可下单'): ['can_order'],
|
||
('product_packaging', '下单备注'): ['remark'],
|
||
('product_packaging', '通用物料编码'): ['common_code'],
|
||
('product_packaging', '通用物料名称'): ['common_name'],
|
||
('product_packaging', '下单日期'): ['order_date'],
|
||
('planning', '成本价'): ['cost_price'],
|
||
('planning', '新旧品'): ['planning_new_old'],
|
||
('data_summary', '总成本'): ['total_cost'],
|
||
('products', '成本价'): ['cost_price'],
|
||
('product_new', '产品总成本'): ['total_cost'],
|
||
('product_new', '产品总货款'): ['total_payment'],
|
||
('product_new', '是否含运'): ['freight_included'],
|
||
('bom', '成本价'): ['cost_price'],
|
||
}
|
||
|
||
|
||
def _field_resp_keys(page, key):
|
||
"""返回某字段应剔除的响应 key 列表(含 key 本身)。"""
|
||
canonical = _canonical_page(page)
|
||
resp = FIELD_RESP_KEYS.get((canonical, key), [])
|
||
keys = [key] + [r for r in resp if r != key]
|
||
return keys
|
||
|
||
|
||
def _canonical_page(page):
|
||
return _FIELD_REGISTRY_ALIASES.get(page, page)
|
||
|
||
|
||
def _page_aliases(page):
|
||
canonical = _canonical_page(page)
|
||
return _PAGE_ALIASES.get(canonical, [canonical])
|
||
|
||
|
||
def _runtime_page(page):
|
||
canonical = _canonical_page(page)
|
||
return _RUNTIME_PAGE.get(canonical, canonical)
|
||
|
||
|
||
def get_field_list(page):
|
||
"""返回页面字段清单(list of {key, sensitive}),未注册的页面返回空列表。"""
|
||
return FIELD_REGISTRY.get(_canonical_page(page), [])
|
||
|
||
|
||
def is_admin_role(role):
|
||
return role in ('admin', 'super_admin')
|
||
|
||
|
||
def _load_field_config(user_id, role, page):
|
||
"""读取角色、岗位、用户三级字段权限配置。"""
|
||
aliases = _page_aliases(page)
|
||
placeholders = ','.join(['%s'] * len(aliases))
|
||
role_perm, position_perm, user_perm = {}, {}, {}
|
||
explicit = set()
|
||
|
||
role_rows = query(
|
||
f"SELECT operation, permission FROM role_operation_permissions "
|
||
f"WHERE role=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'",
|
||
tuple(['viewer' if role == '查看者' else role] + aliases)
|
||
)
|
||
for row in role_rows:
|
||
parts = row['operation'].split(':', 2)
|
||
if len(parts) == 3:
|
||
role_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission'])
|
||
explicit.add(parts[1])
|
||
|
||
ensure_position_permissions()
|
||
position = user_position_code(user_id)
|
||
if position:
|
||
position_rows = query(
|
||
f"SELECT operation, permission FROM position_operation_permissions "
|
||
f"WHERE position_code=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'",
|
||
tuple([position] + aliases)
|
||
)
|
||
for row in position_rows:
|
||
parts = row['operation'].split(':', 2)
|
||
if len(parts) == 3:
|
||
position_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission'])
|
||
explicit.add(parts[1])
|
||
|
||
user_rows = query(
|
||
f"SELECT operation, permission FROM user_permissions "
|
||
f"WHERE user_id=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'",
|
||
tuple([user_id] + aliases)
|
||
)
|
||
for row in user_rows:
|
||
parts = row['operation'].split(':', 2)
|
||
if len(parts) == 3:
|
||
user_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission'])
|
||
explicit.add(parts[1])
|
||
|
||
return role_perm, position_perm, user_perm, explicit
|
||
|
||
|
||
def _resolve_field_value(user_perm, position_perm, role_perm, field_key, operation, default_allow):
|
||
"""字段权限优先级:用户例外 > 岗位权限 > 角色权限 > 页面默认。"""
|
||
user_value = user_perm.get(field_key, {}).get(operation)
|
||
if user_value is not None:
|
||
return bool(user_value)
|
||
position_value = position_perm.get(field_key, {}).get(operation)
|
||
if position_value is not None:
|
||
return bool(position_value)
|
||
role_value = role_perm.get(field_key, {}).get(operation)
|
||
if role_value is not None:
|
||
return bool(role_value)
|
||
return bool(default_allow)
|
||
|
||
|
||
def field_perms_for_page(user_id, role, page, page_view=True, page_edit=False):
|
||
"""返回合并角色、岗位、用户后的字段权限。"""
|
||
fields = get_field_list(page)
|
||
if not fields:
|
||
return {}
|
||
if is_admin_role(role):
|
||
return {field['key']: {'view': True, 'edit': True} for field in fields}
|
||
|
||
role_perm, position_perm, user_perm, _ = _load_field_config(user_id, role, page)
|
||
result = {}
|
||
for field in fields:
|
||
key = field['key']
|
||
result[key] = {
|
||
'view': _resolve_field_value(user_perm, position_perm, role_perm, key, 'view', page_view),
|
||
'edit': _resolve_field_value(user_perm, position_perm, role_perm, key, 'edit', page_edit),
|
||
}
|
||
return result
|
||
|
||
|
||
def role_field_perms_for_page(role, page):
|
||
"""仅角色级字段权限,用于角色权限矩阵。"""
|
||
fields = get_field_list(page)
|
||
if not fields:
|
||
return {}
|
||
if is_admin_role(role):
|
||
return {field['key']: {'view': True, 'edit': True} for field in fields}
|
||
aliases = _page_aliases(page)
|
||
placeholders = ','.join(['%s'] * len(aliases))
|
||
role_perm = {}
|
||
rows = query(
|
||
f"SELECT operation, permission FROM role_operation_permissions "
|
||
f"WHERE role=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'",
|
||
tuple(['viewer' if role == '查看者' else role] + aliases)
|
||
)
|
||
for row in rows:
|
||
parts = row['operation'].split(':', 2)
|
||
if len(parts) == 3:
|
||
role_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission'])
|
||
return {
|
||
field['key']: {
|
||
'view': _resolve_field_value({}, {}, role_perm, field['key'], 'view', True),
|
||
'edit': _resolve_field_value({}, {}, role_perm, field['key'], 'edit', False),
|
||
}
|
||
for field in fields
|
||
}
|
||
|
||
|
||
def inherited_field_perms_for_page(user_id, role, page):
|
||
"""角色+岗位的字段继承值,不包含用户个人覆盖。"""
|
||
fields = get_field_list(page)
|
||
if not fields:
|
||
return {}
|
||
if is_admin_role(role):
|
||
return {field['key']: {'view': True, 'edit': True} for field in fields}
|
||
role_perm, position_perm, _, _ = _load_field_config(user_id, role, page)
|
||
return {
|
||
field['key']: {
|
||
'view': _resolve_field_value({}, position_perm, role_perm, field['key'], 'view', True),
|
||
'edit': _resolve_field_value({}, position_perm, role_perm, field['key'], 'edit', False),
|
||
}
|
||
for field in fields
|
||
}
|
||
|
||
def filter_row(page, row, user_id, role, page_view=True, page_edit=False):
|
||
"""对单个 dict 按字段 view 权限剔除无权查看的字段(原地修改并返回)。"""
|
||
if row is None or is_admin_role(role):
|
||
return row
|
||
perms = field_perms_for_page(user_id, role, page, page_view, page_edit)
|
||
if not perms:
|
||
return row
|
||
for key in list(row.keys()):
|
||
# 响应字段可能用中文显示名,也可能用英文 DB 列名;统一按映射剔除
|
||
matched = None
|
||
for fkey, fperm in perms.items():
|
||
if key in _field_resp_keys(page, fkey):
|
||
matched = (fkey, fperm)
|
||
break
|
||
if matched and not matched[1]['view']:
|
||
del row[key]
|
||
return row
|
||
|
||
|
||
def filter_rows(page, rows, user_id, role, page_view=True, page_edit=False):
|
||
"""对 dict 列表按字段 view 权限剔除字段(原地修改并返回)。"""
|
||
if not rows or is_admin_role(role):
|
||
return rows
|
||
perms = field_perms_for_page(user_id, role, page, page_view, page_edit)
|
||
if not perms:
|
||
return rows
|
||
# 预先构建 响应key -> 是否可查看 的映射,避免逐行重复计算
|
||
resp_visibility = {}
|
||
for fkey, fperm in perms.items():
|
||
for rkey in _field_resp_keys(page, fkey):
|
||
resp_visibility[rkey] = fperm['view']
|
||
for row in rows:
|
||
if not isinstance(row, dict):
|
||
continue
|
||
for key in list(row.keys()):
|
||
if key in resp_visibility and not resp_visibility[key]:
|
||
del row[key]
|
||
return rows
|
||
|
||
|
||
def effective_field_overrides(user_id, role, page_view_by_page=None, page_edit_by_page=None):
|
||
"""返回需要注入前端的字段级权限显式值(用于敏感字段默认隐藏 + 显式配置)。
|
||
|
||
仅返回「敏感字段」和「有显式配置」的字段;其余字段由前端按页面权限默认继承。
|
||
返回结构:{runtime_page: {field_key: {'view': 0/1, 'edit': 0/1}}}。
|
||
"""
|
||
if is_admin_role(role):
|
||
return {}
|
||
page_view_by_page = page_view_by_page or {}
|
||
page_edit_by_page = page_edit_by_page or {}
|
||
overrides = {}
|
||
for page, fields in FIELD_REGISTRY.items():
|
||
role_perm, position_perm, user_perm, explicit = _load_field_config(user_id, role, page)
|
||
pv = page_view_by_page.get(page, True)
|
||
pe = page_edit_by_page.get(page, False)
|
||
page_overrides = {}
|
||
for f in fields:
|
||
key = f['key']
|
||
sensitive = bool(f.get('sensitive'))
|
||
if key not in explicit:
|
||
continue
|
||
page_overrides[key] = {
|
||
'view': 1 if _resolve_field_value(user_perm, position_perm, role_perm, key, 'view', pv) else 0,
|
||
'edit': 1 if _resolve_field_value(user_perm, position_perm, role_perm, key, 'edit', pe) else 0,
|
||
}
|
||
if page_overrides:
|
||
overrides[_runtime_page(page)] = page_overrides
|
||
return overrides
|