Files
erp/plans/stateless-swimming-perlis.md
iven 0baaf5f7ee feat(config): add system configuration module (Phase 3)
Implement the complete erp-config crate with:
- Data dictionaries (CRUD + items management)
- Dynamic menus (tree structure with role filtering)
- System settings (hierarchical: platform > tenant > org > user)
- Numbering rules (concurrency-safe via PostgreSQL advisory_lock)
- Theme and language configuration (via settings store)
- 6 database migrations (dictionaries, menus, settings, numbering_rules)
- Frontend Settings page with 5 tabs (dictionary, menu, numbering, settings, theme)

Refactor: move RBAC functions (require_permission) from erp-auth to erp-core
to avoid cross-module dependencies.

Add 20 new seed permissions for config module operations.
2026-04-11 08:09:19 +08:00

12 KiB
Raw Blame History

Phase 3: 系统配置模块 — 实施计划

Context

Phase 1基础设施和 Phase 2身份与权限已完成。Phase 3 需要实现系统配置模块erp-config提供数据字典、动态菜单、系统参数、编号规则、i18n 框架和主题自定义能力。当前 erp-config 仅为 placeholder。

前置重构:将 RBAC 移至 erp-core

问题: require_permissionerp-auth/src/middleware/rbac.rs 中,但只依赖 erp-coreTenantContextAppError。erp-config 不能依赖 erp-auth架构铁律业务 crate 禁止直接依赖)。

方案:

  1. erp-auth/src/middleware/rbac.rserp-core/src/rbac.rs
  2. erp-core/src/lib.rs 添加 pub mod rbac;
  3. 更新 erp-auth 所有 handler 的 importcrate::middleware::rbacerp_core::rbac
  4. erp-auth/src/middleware/mod.rs 移除 pub mod rbac;

Task 1: erp-config 骨架 + ConfigState

目标: 创建可编译的最小 crate注册到 erp-server。

创建/修改文件:

  • 修改: crates/erp-config/Cargo.toml — 添加完整依赖
  • 修改: crates/erp-config/src/lib.rs — 模块声明 + re-export
  • 创建: crates/erp-config/src/config_state.rs — ConfigState { db, event_bus }
  • 创建: crates/erp-config/src/error.rs — ConfigError 枚举
  • 创建: crates/erp-config/src/module.rs — ConfigModule + 空路由
  • 创建: crates/erp-config/src/dto.rs — 占位
  • 创建: crates/erp-config/src/entity/mod.rs — 占位
  • 创建: crates/erp-config/src/service/mod.rs — 占位
  • 创建: crates/erp-config/src/handler/mod.rs — 占位
  • 修改: crates/erp-server/Cargo.toml — 添加 erp-config 依赖
  • 修改: crates/erp-server/src/state.rs — 添加 FromRef for ConfigState
  • 修改: crates/erp-server/src/main.rs — 注册 ConfigModule

验证: cargo check 通过


Task 2: 数据库迁移6 张表)

目标: 创建所有配置模块表。

创建文件(crates/erp-server/migration/src/

  • m20260412_000012_create_dictionaries.rs — 字典分类表
  • m20260412_000013_create_dictionary_items.rs — 字典项表
  • m20260412_000014_create_menus.rs — 菜单树形表
  • m20260412_000015_create_menu_roles.rs — 菜单-角色关联表
  • m20260412_000016_create_settings.rs — 分层键值配置表
  • m20260412_000017_create_numbering_rules.rs — 编号规则表
  • 修改: lib.rs — 注册新迁移

表结构:

dictionaries

类型 说明
id uuid PK UUIDv7
tenant_id uuid NOT NULL 租户 ID
name string NOT NULL 显示名称
code string NOT NULL 字典键(如 gender
description text NULL 说明
+ 标准审计字段
唯一索引: (tenant_id, code) WHERE deleted_at IS NULL

dictionary_items

类型 说明
id uuid PK UUIDv7
tenant_id uuid NOT NULL
dictionary_id uuid NOT NULL FK → dictionaries
label string NOT NULL 显示标签
value string NOT NULL 存储值
sort_order int DEFAULT 0 排序
color string NULL 颜色标签
+ 标准审计字段
唯一索引: (dictionary_id, value) WHERE deleted_at IS NULL

menus树形自引用

类型 说明
id uuid PK UUIDv7
tenant_id uuid NOT NULL
parent_id uuid NULL 自引用
title string NOT NULL 菜单标题
path string NULL 前端路由
icon string NULL 图标名
sort_order int DEFAULT 0
visible bool DEFAULT true
menu_type string DEFAULT 'page' page/link/button
permission string NULL 所需权限码
+ 标准审计字段

menu_roles复合主键

类型 说明
menu_id uuid NOT NULL PK 组成
role_id uuid NOT NULL PK 组成
tenant_id uuid NOT NULL
+ 标准审计字段
唯一索引: (menu_id, role_id) WHERE deleted_at IS NULL

settings分层配置

类型 说明
id uuid PK UUIDv7
tenant_id uuid NOT NULL
scope string NOT NULL platform/tenant/org/user
scope_id uuid NULL 平台=NULL, 租户=tenant_id 等
setting_key string NOT NULL
setting_value jsonb DEFAULT '{}'
+ 标准审计字段
唯一索引: (scope, scope_id, setting_key) WHERE deleted_at IS NULL

numbering_rules

类型 说明
id uuid PK UUIDv7
tenant_id uuid NOT NULL
name string NOT NULL 规则名称
code string NOT NULL 唯一编码(如 INV
prefix string DEFAULT '' 前缀
date_format string NULL 如 %Y%m%d
seq_length int DEFAULT 4 序列位数
seq_start int DEFAULT 1 起始值
seq_current bigint DEFAULT 0 当前序列
separator string DEFAULT '-' 分隔符
reset_cycle string DEFAULT 'never' never/daily/monthly/yearly
last_reset_date date NULL
+ 标准审计字段
唯一索引: (tenant_id, code) WHERE deleted_at IS NULL

验证: cargo run -p erp-server 启动后 \dt 可见新表


Task 3: SeaORM Entity

目标: 为 6 张表创建 Entity 定义。

创建文件(crates/erp-config/src/entity/

  • mod.rs — 导出所有实体
  • dictionary.rs — Dictionary Entity
  • dictionary_item.rs — DictionaryItem Entity
  • menu.rs — Menu Entity
  • menu_role.rs — MenuRole Entity复合主键模式参考 role_permission
  • setting.rs — Setting Entity
  • numbering_rule.rs — NumberingRule Entity

模式: 参考 erp-auth/src/entity/role.rs,包含 Relation 和 Related 实现。

验证: cargo check 通过


Task 4: DTO 定义

目标: 定义所有配置端点的请求/响应类型。

修改文件: crates/erp-config/src/dto.rs

包含:

  • 字典 DTODictionaryResp, DictionaryItemResp, CreateDictionaryReq, UpdateDictionaryReq, CreateDictionaryItemReq, UpdateDictionaryItemReq
  • 菜单 DTOMenuResp含 children 递归), CreateMenuReq, UpdateMenuReq, BatchSaveMenuReq
  • 设置 DTOSettingResp, UpdateSettingReq
  • 编号规则 DTONumberingRuleResp, CreateNumberingRuleReq, UpdateNumberingRuleReq, GenerateNumberResp
  • 主题 DTOThemeResp, UpdateThemeReq委托 settings 存储)
  • 语言 DTOLanguageResp, UpdateLanguageReq委托 settings 存储)

验证: cargo check 通过


Task 5: Service — 字典 + 系统参数

创建文件(crates/erp-config/src/service/

  • dictionary_service.rs — CRUD + 字典项管理 + 按 code 查询
  • setting_service.rs — 分层读取User>Org>Tenant>Platform+ 写入

关键逻辑:

  • SettingService::get 实现分层覆盖查找
  • SettingService::set 使用 upsert 语义INSERT ON CONFLICT UPDATE
  • DictionaryService 遵循 RoleService 的无状态模式

Task 6: Service — 菜单 + 编号规则

创建文件(crates/erp-config/src/service/

  • menu_service.rs — 菜单树构建 + 角色过滤 + 批量保存
  • numbering_service.rs — 编号规则 CRUD + generate_number

关键逻辑:

  • MenuService::get_menu_tree — 按 role_ids 过滤 menu_roleHashMap 分组构建树(参考 OrgService 的 build_org_tree
  • NumberingService::generate_number — PostgreSQL advisory_lock + 事务内序列递增:
    SELECT pg_advisory_xact_lock(hashtext($1), $2::int)
    -- $1 = rule_code, $2 = hash(tenant_id)
    
    生成格式:{prefix}{separator}{date}{separator}{seq_padded}

Task 7: Handler 层

创建文件(crates/erp-config/src/handler/

  • dictionary_handler.rs — 5 个端点
  • menu_handler.rs — 2 个端点
  • setting_handler.rs — 2 个端点
  • numbering_handler.rs — 4 个端点(含 generate
  • theme_handler.rs — 2 个端点(委托 SettingService
  • language_handler.rs — 2 个端点(委托 SettingService

端点映射:

GET/POST  /config/dictionaries
PUT/DELETE /config/dictionaries/{id}
GET/PUT   /config/menus
GET/PUT   /config/settings/{key}
GET/POST  /config/numbering-rules
PUT       /config/numbering-rules/{id}
POST      /config/numbering-rules/{id}/generate
GET/PUT   /config/themes
GET       /config/languages
PUT       /config/languages/{code}

Task 8: 模块注册 + 种子数据

修改文件:

  • crates/erp-config/src/module.rs — 填充真实路由
  • crates/erp-auth/src/service/seed.rs — 添加配置模块权限dictionary/menu/setting/numbering/theme/language
  • crates/erp-server/src/main.rs — 确认路由集成

新增种子权限17 个):

  • dictionary: create/read/update/delete/list
  • menu: read/update
  • setting: read/update
  • numbering: create/read/update/generate
  • theme: read/update
  • language: read/update

验证: cargo check + cargo test 通过,服务器启动后 Swagger UI 可见新端点


Task 9: 前端 API 层 + Settings 页面框架

创建文件(apps/web/src/

  • api/dictionaries.ts — 字典 CRUD API
  • api/menus.ts — 菜单 API
  • api/settings.ts — 设置 API
  • api/numberingRules.ts — 编号规则 API
  • pages/Settings.tsx — Tabs 壳页面

修改文件:

  • App.tsx — 替换 settings 占位组件

Tabs 结构: 数据字典 | 菜单配置 | 编号规则 | 系统参数 | 主题设置


Task 10: 前端设置子页面

创建文件(apps/web/src/pages/settings/

  • DictionaryManager.tsx — Table + 展开行显示字典项,参考 Roles.tsx
  • MenuConfig.tsx — Tree + 编辑表单,参考 Organizations.tsx
  • NumberingRules.tsx — Table + Modal CRUD + 生成按钮
  • SystemSettings.tsx — 键值编辑列表
  • ThemeSettings.tsx — 颜色选择器 + 表单

修改文件:

  • Settings.tsx — 导入子组件
  • MainLayout.tsx — 更新设置菜单图标

验证: pnpm dev 启动,访问 /settings 各 tab 可正常交互


依赖图

前置重构RBAC→erp-core
         |
Task 1骨架
         |
Task 2迁移→ Task 3Entity→ Task 4DTO
                                       |
                      +----------------+----------------+
                      |                                 |
               Task 5字典+设置 Service      Task 6菜单+编号 Service
                      |                                 |
                      +----------------+----------------+
                                       |
                                  Task 7Handler
                                       |
                                  Task 8集成+种子)
                                       |
                                  Task 9前端API+壳)
                                       |
                                  Task 10前端页面

验证清单

  • cargo check 全 workspace 通过
  • cargo test --workspace 全部通过
  • Docker 环境正常启动
  • 所有迁移可正/反向执行
  • API 端点可通过 Swagger UI 测试
  • 前端 /settings 页面各 Tab 正常工作
  • 所有代码已提交

关键参考文件

用途 文件路径
Service 模式 crates/erp-auth/src/service/role_service.rs
Handler 模式 crates/erp-auth/src/handler/role_handler.rs
树构建模式 crates/erp-auth/src/service/org_service.rs
迁移模式 crates/erp-server/migration/src/m20260411_000005_create_roles.rs
State 桥接 crates/erp-server/src/state.rs
复合主键 Entity crates/erp-auth/src/entity/role_permission.rs
前端 Table CRUD apps/web/src/pages/Roles.tsx
前端树形展示 apps/web/src/pages/Organizations.tsx
RBAC 工具函数 crates/erp-auth/src/middleware/rbac.rs(待迁移到 erp-core