feat: add permission config and initialization for single-owner CRM
- perm_config.py: role definitions and permission matrix (underscore-based role IDs) - init_permissions.py: permission initialization script - .gitignore: add build artifacts exclusions - Remove multi-org type design, single owner org only
This commit is contained in:
parent
2b921a209d
commit
4c5b2a5716
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
py3/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
*.egg
|
||||||
|
wwwroot/wwwroot
|
||||||
|
mysql.ddl.sql
|
||||||
152
app/init_permissions.py
Normal file
152
app/init_permissions.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
"""Permission initialization for integrated CRM application.
|
||||||
|
|
||||||
|
Reads perm_config.py and registers permissions to database.
|
||||||
|
This should be run during deployment or first-time setup.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
# During deployment
|
||||||
|
python app/init_permissions.py
|
||||||
|
|
||||||
|
# Or called from integrated_crm_app.py init()
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import asyncio
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from appPublic.log import debug, info
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_permission(sor, path, name='', permtype='page'):
|
||||||
|
"""Ensure a permission exists in the database."""
|
||||||
|
recs = await sor.R('permission', {'path': path})
|
||||||
|
if recs:
|
||||||
|
return recs[0].id
|
||||||
|
permid = getID()
|
||||||
|
await sor.C('permission', {
|
||||||
|
'id': permid,
|
||||||
|
'name': name or path.split('/')[-1] or path,
|
||||||
|
'path': path,
|
||||||
|
'permtype': permtype,
|
||||||
|
})
|
||||||
|
return permid
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_role(sor, roleid, name, desc=''):
|
||||||
|
"""Ensure a role exists with the given ID."""
|
||||||
|
recs = await sor.R('role', {'id': roleid})
|
||||||
|
if recs:
|
||||||
|
return recs[0].id
|
||||||
|
await sor.C('role', {
|
||||||
|
'id': roleid,
|
||||||
|
'orgtypeid': '*',
|
||||||
|
'name': name,
|
||||||
|
})
|
||||||
|
return roleid
|
||||||
|
|
||||||
|
|
||||||
|
async def grant_permission(sor, roleid, permid):
|
||||||
|
"""Grant a permission to a role if not already granted."""
|
||||||
|
recs = await sor.R('rolepermission', {'roleid': roleid, 'permid': permid})
|
||||||
|
if not recs:
|
||||||
|
await sor.C('rolepermission', {
|
||||||
|
'id': getID(),
|
||||||
|
'roleid': roleid,
|
||||||
|
'permid': permid,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
async def init_permissions_from_config(dbname, config_module=None):
|
||||||
|
"""Initialize all permissions from perm_config.py.
|
||||||
|
|
||||||
|
CRM是单业主机构系统,所有角色属于同一业主机构。
|
||||||
|
流程:
|
||||||
|
1. 创建约定角色(any/logined/anonymous)和定义的角色
|
||||||
|
2. 注册 PERMISSION_MATRIX 中的路径权限
|
||||||
|
3. 注册 CRUD 路径并授权
|
||||||
|
"""
|
||||||
|
if config_module is None:
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
from app.perm_config import (
|
||||||
|
ROLES, PERMISSION_MATRIX, CRUD_TABLES
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ROLES = config_module.ROLES
|
||||||
|
PERMISSION_MATRIX = config_module.PERMISSION_MATRIX
|
||||||
|
CRUD_TABLES = config_module.CRUD_TABLES
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
# Step 1: 创建约定角色(固定ID,rbac/userperm.py硬编码检查)
|
||||||
|
info('Creating convention roles...')
|
||||||
|
role_ids = {}
|
||||||
|
|
||||||
|
# 约定角色必须用固定ID
|
||||||
|
for fixed_id in ['any', 'logined', 'anonymous']:
|
||||||
|
role_ids[fixed_id] = await ensure_role(sor, fixed_id, fixed_id)
|
||||||
|
|
||||||
|
# Step 2: 创建定义的角色
|
||||||
|
info('Creating defined roles...')
|
||||||
|
for role in ROLES:
|
||||||
|
role_ids[role['id']] = await ensure_role(
|
||||||
|
sor, role['id'], role['name'], role.get('desc', '')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 3: 注册权限矩阵中的路径并授权
|
||||||
|
info('Registering permissions from matrix...')
|
||||||
|
perm_count = 0
|
||||||
|
|
||||||
|
for module, paths in PERMISSION_MATRIX.items():
|
||||||
|
for path_pattern, role_list in paths.items():
|
||||||
|
permid = await ensure_permission(sor, path_pattern,
|
||||||
|
name=f'{module}: {path_pattern}', permtype='module')
|
||||||
|
|
||||||
|
for role_name in role_list:
|
||||||
|
if role_name in role_ids:
|
||||||
|
await grant_permission(sor, role_ids[role_name], permid)
|
||||||
|
perm_count += 1
|
||||||
|
|
||||||
|
# Step 4: 注册 CRUD 路径
|
||||||
|
info('Registering CRUD paths...')
|
||||||
|
for module, tables in CRUD_TABLES.items():
|
||||||
|
for table in tables:
|
||||||
|
crud_path = f'/{module}/{table}/'
|
||||||
|
permid = await ensure_permission(sor, crud_path,
|
||||||
|
name=f'{module}/{table} CRUD', permtype='crud')
|
||||||
|
|
||||||
|
# 根据模块权限矩阵授予权限
|
||||||
|
if module in PERMISSION_MATRIX:
|
||||||
|
for path_pattern, role_list in PERMISSION_MATRIX[module].items():
|
||||||
|
if path_pattern.startswith(f'/{module}'):
|
||||||
|
for role_name in role_list:
|
||||||
|
if role_name in role_ids:
|
||||||
|
await grant_permission(sor, role_ids[role_name], permid)
|
||||||
|
perm_count += 1
|
||||||
|
|
||||||
|
info(f'Permission initialization complete: {perm_count} grants created')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run permission initialization."""
|
||||||
|
import json
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
|
||||||
|
config_path = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
config = getConfig(config_path, {'workdir': config_path})
|
||||||
|
|
||||||
|
# Convert database config to DictObject format expected by DBPools
|
||||||
|
db_config = {}
|
||||||
|
for dbname, dbconf in config.databases.items():
|
||||||
|
db_config[dbname] = DictObject(driver=dbconf['driver'], kwargs=DictObject(**dbconf['kwargs']))
|
||||||
|
|
||||||
|
DBPools(db_config)
|
||||||
|
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
dbname = list(config.databases.keys())[0]
|
||||||
|
loop.run_until_complete(init_permissions_from_config(dbname))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@ -1,5 +1,6 @@
|
|||||||
from ahserver.webapp import webapp
|
from ahserver.webapp import webapp
|
||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
|
import os
|
||||||
|
|
||||||
# Import required modules using the standard pattern
|
# Import required modules using the standard pattern
|
||||||
# Foundation modules
|
# Foundation modules
|
||||||
@ -31,6 +32,17 @@ def init():
|
|||||||
# Load modules in dependency order
|
# Load modules in dependency order
|
||||||
load_appbase()
|
load_appbase()
|
||||||
load_rbac()
|
load_rbac()
|
||||||
|
|
||||||
|
# Initialize permissions from perm_config.py
|
||||||
|
# This ensures all UI/DSPY paths and CRUD paths are registered
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
from app.init_permissions import init_permissions_from_config
|
||||||
|
import asyncio
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
loop.run_until_complete(init_permissions_from_config('crm_db'))
|
||||||
|
loop.close()
|
||||||
|
|
||||||
load_customer_management()
|
load_customer_management()
|
||||||
load_opportunity_management()
|
load_opportunity_management()
|
||||||
load_contract_management()
|
load_contract_management()
|
||||||
|
|||||||
156
app/perm_config.py
Normal file
156
app/perm_config.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
"""CRM Permission Configuration
|
||||||
|
|
||||||
|
单一业主机构内的角色和权限配置。
|
||||||
|
部署时由管理员通过RBAC UI或此配置初始化。
|
||||||
|
|
||||||
|
Convention roles:
|
||||||
|
- any: 任何人(未登录)
|
||||||
|
- logined: 已登录用户
|
||||||
|
- anonymous: 匿名用户
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 单一业主机构
|
||||||
|
# ============================================================
|
||||||
|
# CRM为单业主机构系统,不跨机构。
|
||||||
|
# 所有角色属于同一业主机构内部的不同部门。
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 角色定义
|
||||||
|
# ============================================================
|
||||||
|
ROLES = [
|
||||||
|
# 销售部
|
||||||
|
{'id': 'sales_manager', 'name': '销售经理', 'desc': '团队管理、客户分配、审批'},
|
||||||
|
{'id': 'sales_rep', 'name': '销售代表', 'desc': '客户跟进、商机推进、合同起草'},
|
||||||
|
# 财务部
|
||||||
|
{'id': 'finance_admin', 'name': '财务管理员', 'desc': '所有财务操作、报表'},
|
||||||
|
{'id': 'finance_clerk', 'name': '财务出纳', 'desc': '收款登记、付款处理'},
|
||||||
|
# 管理员
|
||||||
|
{'id': 'admin_superuser', 'name': '超级用户', 'desc': '全部权限,初始化用'},
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 权限矩阵
|
||||||
|
# 格式: {模块名: {路径模式: [有权限的角色列表]}}
|
||||||
|
# ============================================================
|
||||||
|
PERMISSION_MATRIX = {
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 公共路径(登录等)
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'main': {
|
||||||
|
'/main/login.ui': ['any'],
|
||||||
|
'/main/login.dspy': ['any'],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 客户管理模块 - 销售+财务+管理员
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'customer_management': {
|
||||||
|
'/customer_management/**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'finance_admin',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
'/customer_management/customer_handover**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
'/customer_management/customer_pool**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 商机管理模块 - 销售+管理员
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'opportunity_management': {
|
||||||
|
'/opportunity_management/**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 合同管理模块 - 销售+财务+管理员
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'contract_management': {
|
||||||
|
'/contract_management/**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'finance_admin',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
'/contract_management/contract_ai_config**': [
|
||||||
|
'sales_manager',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 财务管理模块 - 财务+管理员
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'financial_management': {
|
||||||
|
'/financial_management/**': [
|
||||||
|
'finance_admin', 'finance_clerk',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
'/financial_management/receivables**': [
|
||||||
|
'sales_manager',
|
||||||
|
'finance_admin', 'finance_clerk',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 工作流审批模块 - 全部内部角色
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'workflow_approval': {
|
||||||
|
'/workflow_approval/**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'finance_admin', 'finance_clerk',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# 统一仪表盘 - 全部内部角色
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
'unified_dashboard': {
|
||||||
|
'/unified_dashboard/**': [
|
||||||
|
'sales_manager', 'sales_rep',
|
||||||
|
'finance_admin', 'finance_clerk',
|
||||||
|
'admin_superuser',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CRUD 路径
|
||||||
|
# 每个模块的表自动生成 CRUD 路径: /{modulename}/{tablename}/
|
||||||
|
# ============================================================
|
||||||
|
CRUD_TABLES = {
|
||||||
|
'customer_management': [
|
||||||
|
'customers', 'customer_pool',
|
||||||
|
'customer_handover', 'customer_handover_items',
|
||||||
|
],
|
||||||
|
'opportunity_management': [
|
||||||
|
'opportunities', 'opportunity_stage_history',
|
||||||
|
'opportunity_predictions', 'sales_stages',
|
||||||
|
],
|
||||||
|
'contract_management': [
|
||||||
|
'contract', 'contract_versions', 'contract_attachment',
|
||||||
|
'contract_milestones', 'contract_ai_config',
|
||||||
|
'orders', 'order_payments',
|
||||||
|
],
|
||||||
|
'financial_management': [
|
||||||
|
'receivables', 'receipts', 'receipt_allocations',
|
||||||
|
'payments', 'financial_vouchers',
|
||||||
|
],
|
||||||
|
'workflow_approval': [
|
||||||
|
'approval_workflow', 'approval_instance',
|
||||||
|
'approval_step', 'approval_task',
|
||||||
|
],
|
||||||
|
'unified_dashboard': [
|
||||||
|
'dashboard_config', 'report_template', 'user_dashboard',
|
||||||
|
],
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user