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:
yumoqing 2026-04-29 12:57:14 +08:00
parent 2b921a209d
commit 4c5b2a5716
4 changed files with 335 additions and 0 deletions

15
.gitignore vendored Normal file
View 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
View 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: 创建约定角色固定IDrbac/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()

View File

@ -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
View 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',
],
}