From 9ef6eb636989e74370ba2a07ba0040eb128e24f5 Mon Sep 17 00:00:00 2001 From: ishenwei Date: Thu, 30 Apr 2026 09:29:21 +0800 Subject: [PATCH] feat(permission): add Chinese verbose_name/help_text to permission models (Phase 4.1 part 6/9) --- apps/permission/models/permission_def.py | 99 ++++++++++++--- apps/permission/models/role.py | 46 ++++++- apps/permission/models/staff_perm.py | 149 +++++++++++++++++++---- 3 files changed, 252 insertions(+), 42 deletions(-) diff --git a/apps/permission/models/permission_def.py b/apps/permission/models/permission_def.py index 280fa10..0fa0d8b 100644 --- a/apps/permission/models/permission_def.py +++ b/apps/permission/models/permission_def.py @@ -6,26 +6,95 @@ from core.models.base import TimeStampedModel class PermissionDef(TimeStampedModel): - code = models.CharField(max_length=150, unique=True) - module = models.CharField(max_length=50, choices=PermissionModule.choices) - sub_module = models.CharField(max_length=50, blank=True, default="") - group_name = models.CharField(max_length=100) - name = models.CharField(max_length=200) - description = models.TextField(blank=True, default="") - value_type = models.CharField(max_length=20, choices=PermissionValueType.choices) - scope_choices = models.JSONField(default=list, blank=True) - integer_min = models.IntegerField(null=True, blank=True) - integer_max = models.IntegerField(null=True, blank=True) - default_value = models.JSONField(default=dict) + code = models.CharField( + max_length=150, + unique=True, + verbose_name="权限编码", + help_text='规则:{module}.{sub_module}.{action}[.{qualifier}]', + ) + module = models.CharField( + max_length=50, + choices=PermissionModule.choices, + verbose_name="一级模块", + help_text="home/property/new_house/client/transaction/data/marketing/hr/contract/trinet/system/mobile/smart_store/recharge", + ) + sub_module = models.CharField( + max_length=50, + blank=True, + default="", + verbose_name="二级模块", + help_text='如「二手&租赁」「商圈精耕」', + ) + group_name = models.CharField( + max_length=100, + verbose_name="分组标题", + help_text='如「私客基础权限」「联系人基础权限」', + ) + name = models.CharField( + max_length=200, + verbose_name="显示名称", + ) + description = models.TextField( + blank=True, + default="", + verbose_name="权限作用描述", + ) + value_type = models.CharField( + max_length=20, + choices=PermissionValueType.choices, + verbose_name="权限值类型", + help_text="BOOLEAN=开关型 / SCOPE=范围型 / INTEGER=数值型", + ) + scope_choices = models.JSONField( + default=list, + blank=True, + verbose_name="可选范围", + help_text='仅 SCOPE 类型有效,可选枚举 code 列表,如 ["none","self","store","company"]', + ) + integer_min = models.IntegerField( + null=True, + blank=True, + verbose_name="最小值", + help_text="仅 INTEGER 类型有效", + ) + integer_max = models.IntegerField( + null=True, + blank=True, + verbose_name="最大值", + help_text="仅 INTEGER 类型有效;NULL=无上限(业务上 0 通常代表不限制)", + ) + default_value = models.JSONField( + default=dict, + verbose_name="默认值", + help_text='系统最小默认值,格式 {"v": }', + ) max_allowed_categories = ArrayField( models.CharField(max_length=50), default=list, blank=True, + verbose_name="可配置角色类别", + help_text="允许配置此权限的角色类别列表,空数组=所有类别均可", + ) + sort_order = models.PositiveIntegerField( + default=0, + verbose_name="排序顺序", + help_text="分组内排序", + ) + is_active = models.BooleanField( + default=True, + verbose_name="是否启用", + help_text="下线权限项置 FALSE,历史记录保留", + ) + is_deprecated = models.BooleanField( + default=False, + verbose_name="是否废弃", + help_text="不再推荐使用但保持兼容", + ) + version = models.PositiveIntegerField( + default=1, + verbose_name="定义版本", + help_text="变更时递增,用于缓存失效", ) - sort_order = models.PositiveIntegerField(default=0) - is_active = models.BooleanField(default=True) - is_deprecated = models.BooleanField(default=False) - version = models.PositiveIntegerField(default=1) class Meta: db_table = "permission_defs" diff --git a/apps/permission/models/role.py b/apps/permission/models/role.py index ac3dbd0..a86146f 100644 --- a/apps/permission/models/role.py +++ b/apps/permission/models/role.py @@ -5,24 +5,48 @@ from core.models.base import SoftDeleteModel, TimeStampedModel class Role(SoftDeleteModel): - name = models.CharField(max_length=100) - category = models.CharField(max_length=30, choices=PermissionRoleCategory.choices) - description = models.TextField(blank=True, default="") + name = models.CharField( + max_length=100, + verbose_name="角色名称", + ) + category = models.CharField( + max_length=30, + choices=PermissionRoleCategory.choices, + verbose_name="角色类别", + help_text="agent=置业顾问 / store_manager=店管 / director=总经 / operator=运营 / custom=自定义", + ) + description = models.TextField( + blank=True, + default="", + verbose_name="角色描述", + ) template_role = models.ForeignKey( "fonrey_permission.Role", null=True, blank=True, on_delete=models.SET_NULL, related_name="derived_roles", + verbose_name="权限模板来源", + help_text='PRD「引用该角色配置」列', + ) + is_system_builtin = models.BooleanField( + default=False, + verbose_name="是否系统内置", + help_text='如「最大权限角色」,不可删除、不可改名', + ) + is_active = models.BooleanField( + default=True, + verbose_name="是否启用", + help_text="FALSE=禁用(员工无法继承该角色权限)", ) - is_system_builtin = models.BooleanField(default=False) - is_active = models.BooleanField(default=True) created_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="permission_roles_created", + verbose_name="创建人", + help_text="角色类别只能由创建者修改", ) updated_by = models.ForeignKey( "org.Staff", @@ -30,6 +54,8 @@ class Role(SoftDeleteModel): blank=True, on_delete=models.SET_NULL, related_name="permission_roles_updated", + verbose_name="最后修改人", + help_text="权限管理审计用", ) class Meta: @@ -61,19 +87,27 @@ class RolePermission(TimeStampedModel): "fonrey_permission.Role", on_delete=models.CASCADE, related_name="permissions", + verbose_name="所属角色", + help_text="稀疏存储:角色删除时级联清理权限值", ) permission_def = models.ForeignKey( "fonrey_permission.PermissionDef", on_delete=models.PROTECT, related_name="role_assignments", + verbose_name="权限定义", + help_text="RESTRICT 防止删除仍被引用的权限项", + ) + value = models.JSONField( + verbose_name="权限值", + help_text='统一格式 {"v": }', ) - value = models.JSONField() updated_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="role_permissions_updated", + verbose_name="最后修改人", ) class Meta: diff --git a/apps/permission/models/staff_perm.py b/apps/permission/models/staff_perm.py index c8e3a60..5d3e57a 100644 --- a/apps/permission/models/staff_perm.py +++ b/apps/permission/models/staff_perm.py @@ -14,23 +14,44 @@ class StaffRole(UUIDPrimaryKeyModel): "org.Staff", on_delete=models.CASCADE, related_name="staff_roles", + verbose_name="所属员工", + help_text="员工删除时级联删除角色关联", ) role = models.ForeignKey( "fonrey_permission.Role", on_delete=models.PROTECT, related_name="staff_links", + verbose_name="角色", + help_text="角色被员工引用时禁止删除", + ) + is_primary = models.BooleanField( + default=False, + verbose_name="是否主角色", + help_text="每个员工有且仅有一个主角色", + ) + assigned_at = models.DateTimeField( + auto_now_add=True, + verbose_name="分配时间", ) - is_primary = models.BooleanField(default=False) - assigned_at = models.DateTimeField(auto_now_add=True) assigned_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="staff_role_assignments_made", + verbose_name="分配操作人", + ) + valid_from = models.DateField( + null=True, + blank=True, + verbose_name="生效日", + help_text='预留未来「定时生效」功能', + ) + valid_until = models.DateField( + null=True, + blank=True, + verbose_name="失效日", ) - valid_from = models.DateField(null=True, blank=True) - valid_until = models.DateField(null=True, blank=True) class Meta: db_table = "staff_roles" @@ -61,27 +82,44 @@ class StaffPermissionOverride(UUIDPrimaryKeyModel): "org.Staff", on_delete=models.CASCADE, related_name="permission_overrides", + verbose_name="所属员工", + help_text="员工删除时级联删除覆盖记录", ) permission_def = models.ForeignKey( "fonrey_permission.PermissionDef", on_delete=models.PROTECT, related_name="staff_overrides", + verbose_name="被覆盖权限项", + ) + value = models.JSONField( + verbose_name="个人权限值", + help_text='统一格式 {"v": }', ) - value = models.JSONField() override_mode = models.CharField( max_length=10, choices=PermissionOverrideMode.choices, default=PermissionOverrideMode.REPLACE, + verbose_name="覆盖模式", + help_text="REPLACE=替换合并值 / RESTRICT=限制上限 / GRANT=仅扩展", + ) + reason = models.TextField( + blank=True, + default="", + verbose_name="备注", + help_text="管理员备注,建议强制填写以便审计", ) - reason = models.TextField(blank=True, default="") modified_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="staff_overrides_modified", + verbose_name="修改人", + ) + modified_at = models.DateTimeField( + auto_now=True, + verbose_name="最近修改时间", ) - modified_at = models.DateTimeField(auto_now=True) class Meta: db_table = "staff_permission_overrides" @@ -103,10 +141,14 @@ class StaffDataScope(UUIDPrimaryKeyModel): "org.Staff", on_delete=models.CASCADE, related_name="data_scopes", + verbose_name="所属员工", + help_text="员工删除时级联删除范围记录", ) scope_type = models.CharField( max_length=20, choices=PermissionDataScopeType.choices, + verbose_name="范围类型", + help_text="self=本人 / group=本组 / store=本门店 / area=本区域 / region=本大区 / company=全公司 / custom_unit=指定节点", ) org_unit = models.ForeignKey( "org.OrgUnit", @@ -114,19 +156,40 @@ class StaffDataScope(UUIDPrimaryKeyModel): blank=True, on_delete=models.PROTECT, related_name="data_scope_grants", + verbose_name="组织节点", + help_text="scope_type=custom_unit 时必填,其他类型为 NULL", + ) + is_readable = models.BooleanField( + default=True, + verbose_name="可读", + ) + is_writable = models.BooleanField( + default=False, + verbose_name="可写", + help_text="默认只读", ) - is_readable = models.BooleanField(default=True) - is_writable = models.BooleanField(default=False) granted_by = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="data_scopes_granted", + verbose_name="授权操作人", + ) + granted_at = models.DateTimeField( + auto_now_add=True, + verbose_name="授权时间", + ) + expires_at = models.DateTimeField( + null=True, + blank=True, + verbose_name="临时授权失效时间", + ) + reason = models.TextField( + blank=True, + default="", + verbose_name="授予原因", ) - granted_at = models.DateTimeField(auto_now_add=True) - expires_at = models.DateTimeField(null=True, blank=True) - reason = models.TextField(blank=True, default="") class Meta: db_table = "staff_data_scopes" @@ -147,14 +210,20 @@ class PermissionChangeLog(UUIDPrimaryKeyModel): target_type = models.CharField( max_length=30, choices=PermissionChangeTargetType.choices, + verbose_name="变更对象类型", + help_text="role / role_permission / staff_role / staff_override / staff_scope", + ) + target_id = models.UUIDField( + verbose_name="变更对象 ID", ) - target_id = models.UUIDField() staff = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="permission_change_logs_affecting", + verbose_name="被影响员工", + help_text="target 是 staff_role/staff_override/staff_scope 时必填", ) role = models.ForeignKey( "fonrey_permission.Role", @@ -162,20 +231,58 @@ class PermissionChangeLog(UUIDPrimaryKeyModel): blank=True, on_delete=models.SET_NULL, related_name="change_logs", + verbose_name="被影响角色", + ) + permission_code = models.CharField( + max_length=150, + blank=True, + default="", + verbose_name="权限编码", + help_text="用 code 而非 FK,避免 PermissionDef 删除后日志丢失", + ) + action = models.CharField( + max_length=20, + choices=PermissionChangeAction.choices, + verbose_name="操作动作", + help_text="create / update / delete / assign / revoke", + ) + old_value = models.JSONField( + null=True, + blank=True, + verbose_name="变更前快照", + ) + new_value = models.JSONField( + null=True, + blank=True, + verbose_name="变更后快照", ) - permission_code = models.CharField(max_length=150, blank=True, default="") - action = models.CharField(max_length=20, choices=PermissionChangeAction.choices) - old_value = models.JSONField(null=True, blank=True) - new_value = models.JSONField(null=True, blank=True) operator = models.ForeignKey( "org.Staff", on_delete=models.PROTECT, related_name="permission_changes_operated", + verbose_name="操作人", + ) + operator_ip = models.GenericIPAddressField( + null=True, + blank=True, + verbose_name="操作来源 IP", + ) + user_agent = models.TextField( + blank=True, + default="", + verbose_name="操作终端 UA", + ) + reason = models.TextField( + blank=True, + default="", + verbose_name="操作原因", + help_text="批量设置角色等场景强制填写", + ) + operated_at = models.DateTimeField( + auto_now_add=True, + verbose_name="操作时间", + help_text="append-only 流水,分区键", ) - operator_ip = models.GenericIPAddressField(null=True, blank=True) - user_agent = models.TextField(blank=True, default="") - reason = models.TextField(blank=True, default="") - operated_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "permission_change_logs"