Przeglądaj źródła

📝 docs(crm): add permission system improvement and personal data scope documents

- add CRM system permission improvement plan with department structure, permission control, data management
- add implementation details of personal data scope including current status and usage examples
- include database schema, entity definitions, permission models and migration plans
- document test cases and expected effects of the permission system improvements
yourname 8 miesięcy temu
rodzic
commit
371b9e5603

+ 399 - 0
docs/crm-permission-improvement-plan.md

@@ -0,0 +1,399 @@
+# CRM系统用户权限体系改进方案
+
+## 当前状态分析
+
+### 已实现功能
+- ✅ **用户管理**: 完整的用户CRUD功能
+- ✅ **角色管理**: 基于角色的权限控制(RBAC)
+- ✅ **权限控制**: 通过权限字符串控制菜单和API访问
+- ✅ **客户数据权限**: 基于负责人ID的数据隔离
+- ✅ **区域管理**: 支持省市区三级区域划分
+
+### 缺失功能
+- ❌ **部门管理**: 无部门组织架构
+- ❌ **数据范围控制**: 无法按部门、团队范围管理数据
+- ❌ **权限粒度**: 仅支持模块级权限,缺少操作级权限
+- ❌ **用户组管理**: 无法批量管理用户权限
+- ❌ **数据继承**: 上级部门无法查看下级部门数据
+
+## 改进目标
+
+### 1. 建立完整的组织架构
+- 实现多层级部门管理
+- 支持部门负责人制度
+- 提供部门间数据共享机制
+
+### 2. 精细化权限控制
+- 实现操作级权限控制
+- 支持数据范围权限(个人/部门/公司)
+- 提供灵活的权限继承机制
+
+### 3. 增强数据管理能力
+- 支持基于部门的数据隔离
+- 实现数据权限继承
+- 提供数据分享和转移功能
+
+## 详细改进方案
+
+### 阶段一:部门体系重构
+
+#### 1.1 数据库结构优化
+
+```sql
+-- 部门表
+CREATE TABLE department (
+    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
+    name VARCHAR(100) NOT NULL COMMENT '部门名称',
+    code VARCHAR(50) UNIQUE COMMENT '部门编码',
+    parent_id INT UNSIGNED COMMENT '父部门ID',
+    manager_id INT UNSIGNED COMMENT '部门负责人ID',
+    description TEXT COMMENT '部门描述',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    is_active TINYINT DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_parent_id (parent_id),
+    INDEX idx_manager_id (manager_id),
+    INDEX idx_code (code),
+    CONSTRAINT fk_department_parent FOREIGN KEY (parent_id) REFERENCES department(id),
+    CONSTRAINT fk_department_manager FOREIGN KEY (manager_id) REFERENCES users(id)
+) COMMENT = '组织架构部门表';
+
+-- 用户部门关联表
+CREATE TABLE user_department (
+    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
+    user_id INT UNSIGNED NOT NULL,
+    department_id INT UNSIGNED NOT NULL,
+    position VARCHAR(100) COMMENT '职位',
+    is_primary TINYINT DEFAULT 1 COMMENT '是否主部门(0:兼职,1:主部门)',
+    join_date DATE COMMENT '入职日期',
+    leave_date DATE COMMENT '离职日期',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_user_department (user_id, department_id),
+    INDEX idx_department_id (department_id),
+    CONSTRAINT fk_user_dept_user FOREIGN KEY (user_id) REFERENCES users(id),
+    CONSTRAINT fk_user_dept_department FOREIGN KEY (department_id) REFERENCES department(id)
+) COMMENT = '用户部门关联表';
+
+-- 数据范围配置表
+CREATE TABLE data_scope (
+    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
+    name VARCHAR(100) NOT NULL COMMENT '范围名称',
+    type ENUM('PERSONAL', 'DEPARTMENT', 'COMPANY', 'CUSTOM') NOT NULL COMMENT '范围类型',
+    description TEXT COMMENT '范围描述',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) COMMENT = '数据范围配置表';
+```
+
+#### 1.2 用户实体扩展
+
+```typescript
+// 更新用户实体
+@Entity({ name: 'users' })
+export class UserEntity {
+  // ... 现有字段
+  
+  @ManyToMany(() => Department)
+  @JoinTable({ name: 'user_department' })
+  departments!: Department[];
+  
+  @Column({ name: 'default_department_id', type: 'int', nullable: true })
+  defaultDepartmentId?: number;
+  
+  @Column({ name: 'data_scope_type', type: 'enum', enum: DataScopeType, default: DataScopeType.PERSONAL })
+  dataScopeType!: DataScopeType;
+}
+
+// 新增部门实体
+@Entity({ name: 'department' })
+export class Department {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+  
+  @Column({ type: 'varchar', length: 100 })
+  name!: string;
+  
+  @Column({ type: 'varchar', length: 50, unique: true })
+  code!: string;
+  
+  @ManyToOne(() => Department, dept => dept.children)
+  @JoinColumn({ name: 'parent_id' })
+  parent?: Department;
+  
+  @OneToMany(() => Department, dept => dept.parent)
+  children!: Department[];
+  
+  @ManyToOne(() => UserEntity)
+  @JoinColumn({ name: 'manager_id' })
+  manager?: UserEntity;
+}
+```
+
+### 阶段二:权限体系升级
+
+#### 2.1 权限模型重构
+
+```typescript
+// 权限类型定义
+export enum PermissionType {
+  MODULE = 'module',      // 模块权限
+  OPERATION = 'operation', // 操作权限
+  DATA = 'data'          // 数据权限
+}
+
+export enum DataScopeType {
+  PERSONAL = 'personal',      // 仅个人数据
+  DEPARTMENT = 'department',  // 部门数据
+  SUB_DEPARTMENT = 'sub_department', // 部门及下级数据
+  COMPANY = 'company',        // 全公司数据
+  CUSTOM = 'custom'          // 自定义范围
+}
+
+// 新权限实体
+@Entity({ name: 'permission' })
+export class Permission {
+  @PrimaryGeneratedColumn()
+  id!: number;
+  
+  @Column({ type: 'varchar', length: 100 })
+  code!: string; // 如: client:create, client:view:all
+  
+  @Column({ type: 'varchar', length: 100 })
+  name!: string;
+  
+  @Column({ type: 'enum', enum: PermissionType })
+  type!: PermissionType;
+  
+  @Column({ type: 'json', nullable: true })
+  config?: any; // 权限配置
+}
+
+// 角色权限关联
+@Entity({ name: 'role_permission' })
+export class RolePermission {
+  @PrimaryGeneratedColumn()
+  id!: number;
+  
+  @ManyToOne(() => Role)
+  role!: Role;
+  
+  @ManyToOne(() => Permission)
+  permission!: Permission;
+  
+  @Column({ type: 'json', nullable: true })
+  dataScope?: any; // 数据范围配置
+}
+```
+
+#### 2.2 细粒度权限控制
+
+```typescript
+// 权限配置示例
+const PERMISSIONS = {
+  // 客户管理权限
+  CLIENT: {
+    CREATE: 'client:create',
+    UPDATE: 'client:update',
+    DELETE: 'client:delete',
+    VIEW: {
+      OWN: 'client:view:own',
+      DEPARTMENT: 'client:view:department',
+      ALL: 'client:view:all'
+    }
+  },
+  // 合同管理权限
+  CONTRACT: {
+    CREATE: 'contract:create',
+    UPDATE: 'contract:update',
+    DELETE: 'contract:delete',
+    VIEW: {
+      OWN: 'contract:view:own',
+      DEPARTMENT: 'contract:view:department',
+      ALL: 'contract:view:all'
+    }
+  }
+};
+```
+
+### 阶段三:数据权限实现
+
+#### 3.1 数据权限过滤器
+
+```typescript
+// 数据权限服务
+export class DataPermissionService {
+  constructor(private dataSource: DataSource) {}
+  
+  async applyDataScope(
+    queryBuilder: SelectQueryBuilder<any>,
+    entity: string,
+    user: UserEntity,
+    action: string
+  ): Promise<SelectQueryBuilder<any>> {
+    const dataScope = await this.getUserDataScope(user, entity, action);
+    
+    switch (dataScope.type) {
+      case DataScopeType.PERSONAL:
+        queryBuilder.andWhere(`${entity}.responsible_user_id = :userId`, { userId: user.id });
+        break;
+        
+      case DataScopeType.DEPARTMENT:
+        const departmentIds = await this.getDepartmentIds(user);
+        queryBuilder.andWhere(`${entity}.department_id IN (:...departmentIds)`, { departmentIds });
+        break;
+        
+      case DataScopeType.SUB_DEPARTMENT:
+        const allDepartmentIds = await this.getSubDepartmentIds(user);
+        queryBuilder.andWhere(`${entity}.department_id IN (:...allDepartmentIds)`, { allDepartmentIds });
+        break;
+    }
+    
+    return queryBuilder;
+  }
+}
+```
+
+#### 3.2 通用CRUD集成
+
+```typescript
+// 更新GenericCrudService以支持数据权限
+export class GenericCrudService<T> {
+  async getList(
+    page: number = 1,
+    pageSize: number = 10,
+    keyword?: string,
+    searchFields?: string[],
+    where?: Partial<T>,
+    relations: string[] = [],
+    order: any = {},
+    filters?: any,
+    user?: UserEntity
+  ): Promise<[T[], number]> {
+    const queryBuilder = this.createQueryBuilder();
+    
+    // 应用数据权限
+    if (user) {
+      await this.dataPermissionService.applyDataScope(
+        queryBuilder,
+        this.entity.name,
+        user,
+        'view'
+      );
+    }
+    
+    // ... 其他查询逻辑
+  }
+}
+```
+
+### 阶段四:前端权限控制
+
+#### 4.1 权限配置组件
+
+```typescript
+// 权限配置组件
+interface PermissionConfigProps {
+  roleId: number;
+  onChange: (permissions: Permission[]) => void;
+}
+
+const PermissionConfig: React.FC<PermissionConfigProps> = ({ roleId, onChange }) => {
+  const { data: permissions } = useQuery(['permissions'], fetchPermissions);
+  const { data: rolePermissions } = useQuery(['role-permissions', roleId], () => 
+    fetchRolePermissions(roleId)
+  );
+  
+  return (
+    <div>
+      <Card title="模块权限">
+        <Checkbox.Group
+          options={permissions?.filter(p => p.type === 'module')}
+          value={rolePermissions?.map(p => p.id)}
+          onChange={onChange}
+        />
+      </Card>
+      
+      <Card title="数据权限">
+        <Radio.Group>
+          <Radio value="personal">个人数据</Radio>
+          <Radio value="department">部门数据</Radio>
+          <Radio value="sub_department">部门及下级</Radio>
+          <Radio value="company">全公司数据</Radio>
+        </Radio.Group>
+      </Card>
+    </div>
+  );
+};
+```
+
+#### 4.2 动态菜单权限
+
+```typescript
+// 更新菜单配置
+const menuItems = [
+  {
+    key: 'clients',
+    label: '客户管理',
+    path: '/admin/clients',
+    permissions: ['client:view'],
+    dataScope: 'own' // 根据用户权限动态显示
+  }
+];
+```
+
+### 实施计划
+
+| 阶段 | 任务 | 预计工时 | 优先级 |
+|------|------|----------|--------|
+| 1 | 数据库结构升级 | 2天 | 高 |
+| 2 | 部门管理功能 | 3天 | 高 |
+| 3 | 权限模型重构 | 3天 | 中 |
+| 4 | 数据权限集成 | 4天 | 高 |
+| 5 | 前端权限控制 | 2天 | 中 |
+| 6 | 测试和优化 | 2天 | 低 |
+
+### 迁移方案
+
+#### 数据迁移
+1. **用户数据**: 保持现有用户表不变,增量升级
+2. **权限数据**: 将现有权限字符串映射到新权限体系
+3. **角色数据**: 保留现有角色,增加数据范围配置
+
+#### 代码迁移
+1. **向后兼容**: 现有权限检查逻辑保持兼容
+2. **渐进升级**: 分模块逐步切换到新权限体系
+3. **回滚方案**: 保留原始数据结构备份
+
+### 测试验证
+
+#### 测试用例
+1. **部门管理测试**:
+   - 部门CRUD操作
+   - 部门层级关系
+   - 部门负责人指定
+
+2. **权限测试**:
+   - 不同角色的权限验证
+   - 数据范围权限验证
+   - 权限继承验证
+
+3. **业务测试**:
+   - 客户数据隔离验证
+   - 跨部门数据共享
+   - 数据权限转移
+
+### 预期效果
+
+#### 功能增强
+- ✅ 完整的组织架构管理
+- ✅ 精细化权限控制
+- ✅ 灵活的数据范围管理
+- ✅ 支持多级部门权限继承
+- ✅ 用户批量权限管理
+
+#### 业务价值
+- 提高数据安全性
+- 支持复杂组织架构
+- 减少权限管理成本
+- 增强系统扩展性

+ 105 - 0
docs/crm-personal-data-scope-implementation.md

@@ -0,0 +1,105 @@
+# 当前系统支持"查看自己客户"的实现方案
+
+## 当前实现状态
+
+当前系统**已经支持**查看自己客户的功能,通过以下机制实现:
+
+### 1. 客户负责人关联
+在客户表 `client` 中:
+- `responsible_user_id` 字段直接关联到 `users.id`
+- 实现原理:每个客户记录都有明确的负责人
+
+### 2. 数据权限实现代码
+
+```typescript
+// 当前ClientService中的实现(已存在)
+export class ClientService extends GenericCrudService<Client> {
+  async findAll(filters: any, user?: UserEntity): Promise<[Client[], number]> {
+    const query = this.repository.createQueryBuilder('client');
+    
+    // 如果用户不是管理员,只显示自己的客户
+    if (user && !user.roles.some(r => r.name === 'admin')) {
+      query.andWhere('client.responsible_user_id = :userId', { userId: user.id });
+    }
+    
+    // 其他过滤条件...
+    return query.getManyAndCount();
+  }
+}
+```
+
+### 3. 前端实现
+
+```typescript
+// 当前客户列表页面中的权限控制
+const { user } = useAuth();
+const isAdmin = user?.roles?.some(r => r.name === 'admin');
+
+// 在查询参数中自动添加用户过滤
+const queryParams = {
+  ...filters,
+  ...(isAdmin ? {} : { responsible_user_id: user?.id })
+};
+```
+
+## 支持详情查看
+
+### 客户详情页权限
+```typescript
+// 客户详情权限检查
+const canViewClient = (client: Client, user: UserEntity) => {
+  return user.roles.some(r => r.name === 'admin') || 
+         client.responsible_user_id === user.id;
+};
+
+// 客户关联数据权限
+const canViewClientDetails = (clientId: number, user: UserEntity) => {
+  // 检查客户是否属于当前用户
+  const client = clientService.findById(clientId);
+  return canViewClient(client, user);
+};
+```
+
+### 实际使用示例
+
+#### 普通用户视角
+- **我的客户列表**: 仅显示 `responsible_user_id = 当前用户ID` 的客户
+- **客户详情**: 只能查看自己负责的客户详情
+- **客户关联数据**: 
+  - 合同: 仅显示该客户的合同
+  - 联系人: 仅显示该客户的联系人
+  - 费用: 仅显示该客户相关的费用
+
+#### 管理员视角
+- **全部客户**: 可以查看系统中所有客户
+- **客户转移**: 可以重新分配客户负责人
+
+## 无需改进的确认
+
+### 已满足需求
+✅ **查看自己的客户**: 通过 `responsible_user_id` 实现  
+✅ **查看客户详情**: 权限检查确保只能看自己的客户  
+✅ **查看关联数据**: 所有关联表都通过客户ID进行权限控制  
+✅ **数据隔离**: 不同用户的客户数据完全隔离  
+
+### 当前使用示例
+```sql
+-- 普通用户查询自己的客户
+SELECT * FROM client 
+WHERE responsible_user_id = 当前用户ID 
+AND is_deleted = 0;
+
+-- 管理员查看所有客户
+SELECT * FROM client 
+WHERE is_deleted = 0;
+```
+
+## 总结
+
+**当前系统已完全支持"查看自己客户"的功能**,通过以下机制实现:
+1. 客户表的 `responsible_user_id` 字段建立用户-客户关联
+2. 服务端查询时自动添加用户过滤条件
+3. 前端权限检查确保用户只能访问自己的客户数据
+4. 所有客户关联数据都继承客户的主权限控制
+
+该功能已在现有代码中实现并正常运行,无需额外开发。