Files
fonrey/apps/permission/models/staff_perm.py

316 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from core.enums import (
PermissionChangeAction,
PermissionChangeTargetType,
PermissionDataScopeType,
PermissionOverrideMode,
)
from core.models.base import TimeStampedModel, UUIDPrimaryKeyModel
class StaffRole(UUIDPrimaryKeyModel):
staff = models.ForeignKey(
"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="分配时间",
)
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="失效日",
)
class Meta:
db_table = "staff_roles"
verbose_name = "员工角色"
verbose_name_plural = "员工角色"
constraints = [
models.UniqueConstraint(
fields=["staff", "role"],
name="uq_staff_roles",
),
models.UniqueConstraint(
fields=["staff"],
condition=models.Q(is_primary=True),
name="uq_staff_roles_primary",
),
]
indexes = [
models.Index(fields=["role"], name="idx_staff_roles_role"),
]
def __str__(self) -> str:
marker = " [primary]" if self.is_primary else ""
return f"{self.staff_id}{self.role_id}{marker}"
class StaffPermissionOverride(UUIDPrimaryKeyModel):
staff = models.ForeignKey(
"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>}',
)
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="管理员备注,建议强制填写以便审计",
)
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="最近修改时间",
)
class Meta:
db_table = "staff_permission_overrides"
verbose_name = "个人权限覆盖"
verbose_name_plural = "个人权限覆盖"
constraints = [
models.UniqueConstraint(
fields=["staff", "permission_def"],
name="uq_staff_overrides",
),
]
indexes = [
models.Index(fields=["staff"], name="idx_staff_overrides_staff"),
]
class StaffDataScope(UUIDPrimaryKeyModel):
staff = models.ForeignKey(
"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",
null=True,
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="默认只读",
)
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="授予原因",
)
class Meta:
db_table = "staff_data_scopes"
verbose_name = "员工数据范围"
verbose_name_plural = "员工数据范围"
indexes = [
models.Index(fields=["staff"], name="idx_data_scopes_staff"),
models.Index(fields=["org_unit"], name="idx_data_scopes_org"),
models.Index(
fields=["expires_at"],
name="idx_data_scopes_expires",
condition=models.Q(expires_at__isnull=False),
),
]
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",
)
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",
null=True,
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="变更后快照",
)
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 流水,分区键",
)
class Meta:
db_table = "permission_change_logs"
verbose_name = "权限变更流水"
verbose_name_plural = "权限变更流水"
ordering = ["-operated_at"]
indexes = [
models.Index(
fields=["staff", "-operated_at"],
name="idx_perm_log_staff",
condition=models.Q(staff__isnull=False),
),
models.Index(
fields=["role", "-operated_at"],
name="idx_perm_log_role",
condition=models.Q(role__isnull=False),
),
models.Index(
fields=["target_type", "target_id", "-operated_at"],
name="idx_perm_log_target",
),
models.Index(
fields=["operator", "-operated_at"],
name="idx_perm_log_operator",
),
models.Index(fields=["-operated_at"], name="idx_perm_log_time"),
]
def delete(self, *args, **kwargs):
raise NotImplementedError("PermissionChangeLog is append-only and cannot be deleted.")