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", ) role = models.ForeignKey( "fonrey_permission.Role", on_delete=models.PROTECT, related_name="staff_links", ) 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", ) valid_from = models.DateField(null=True, blank=True) valid_until = models.DateField(null=True, blank=True) 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", ) permission_def = models.ForeignKey( "fonrey_permission.PermissionDef", on_delete=models.PROTECT, related_name="staff_overrides", ) value = models.JSONField() override_mode = models.CharField( max_length=10, choices=PermissionOverrideMode.choices, default=PermissionOverrideMode.REPLACE, ) 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", ) modified_at = models.DateTimeField(auto_now=True) 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", ) scope_type = models.CharField( max_length=20, choices=PermissionDataScopeType.choices, ) org_unit = models.ForeignKey( "org.OrgUnit", null=True, blank=True, on_delete=models.PROTECT, related_name="data_scope_grants", ) 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", ) 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" 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, ) target_id = models.UUIDField() staff = models.ForeignKey( "org.Staff", null=True, blank=True, on_delete=models.SET_NULL, related_name="permission_change_logs_affecting", ) role = models.ForeignKey( "fonrey_permission.Role", null=True, blank=True, on_delete=models.SET_NULL, related_name="change_logs", ) 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", ) 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" 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.")