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.serverenv import ServerEnv
|
||||
import os
|
||||
|
||||
# Import required modules using the standard pattern
|
||||
# Foundation modules
|
||||
@ -31,6 +32,17 @@ def init():
|
||||
# Load modules in dependency order
|
||||
load_appbase()
|
||||
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_opportunity_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