加入了 GVSDSDK 模块,进行了 QModel 兼容层的尝试,生产环境可用

This commit is contained in:
2026-06-16 00:13:10 +08:00
parent 9d9cfa53a4
commit 63e0f4edfc
68 changed files with 10256 additions and 1143 deletions

813
gvsdsdk/models.py Normal file
View File

@@ -0,0 +1,813 @@
"""GVS-DSDK 数据模型模块
将 SAAS 主服务器的用户表User、权限表Permission、角色表Role
用户角色关联表UserRole、角色权限关联表RolePermission
岗位模板表PositionTemplate、岗位模板权限关联表PositionTemplatePermission
人事职务标签表Position、员工部门关联表UserDept封装为独立的 Django ORM 模型,
供子服务器直接进行跨服务器 ORM 查询。
权限体系四大分层:
1. 组织人事层Tenant → Company → Department员工通过 UserDept 归属部门
2. 岗位模板层PositionTemplate + PositionTemplatePermission权限初始化快照
3. 角色权限载体层Role + RolePermission运行时鉴权实体
4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性)
使用方式(子服务器):
from gvsdsdk.models import User, Permission, Role, UserRole, RolePermission
# 查询所有启用的用户
users = User.objects.using('saas').filter(UserAccountLicense=1)
# 或配置 DatabaseRouter 后直接使用
users = User.objects.filter(UserAccountLicense=1)
设计原则:
1. 独立:不依赖 gvsds 内部模块,可在任意 Django 项目中独立使用
2. 非托管managed=False不创建/迁移表,仅映射已有表结构
3. PascalCase字段名、方法名遵循帕斯卡命名法
4. 兼容db_table、db_column 与 SAAS 主库完全一致
5. 租户隔离:所有权限相关数据依附于独立租户空间,不同租户互相不可见
"""
import uuid as _uuid
import datetime as _datetime
from django.db import models
from django.contrib.auth.models import BaseUserManager
from gvsdsdk.model_base import QModel
from gvsdsdk.model_utils import UUIDObj
# === UUID 字段 ===
class FixedBinaryField(models.BinaryField):
"""定长二进制字段:映射 MySQL BINARY(N),可用于主键"""
description = "Fixed-length binary field"
def __init__(self, *args, **kwargs):
self.binary_length = kwargs.pop('binary_length', 16)
kwargs.setdefault('max_length', self.binary_length)
kwargs.setdefault('editable', False)
super().__init__(*args, **kwargs)
def db_type(self, connection):
if connection.vendor == 'mysql':
return f'BINARY({self.binary_length})'
elif connection.vendor == 'sqlite':
return 'BLOB'
elif connection.vendor == 'postgresql':
return 'BYTEA'
return f'BINARY({self.binary_length})'
def rel_db_type(self, connection):
return self.db_type(connection)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
kwargs['binary_length'] = self.binary_length
return name, path, args, kwargs
class UUIDField(FixedBinaryField):
"""UUID 字段:数据库存储为 BINARY(16)Python 侧自动转换 str/bytes/uuid.UUID"""
description = "UUID stored as BINARY(16)"
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 16)
kwargs.setdefault('editable', False)
super().__init__(*args, **kwargs)
def db_type(self, connection):
vendor = connection.vendor
if vendor == 'mysql':
return 'BINARY(16)'
elif vendor == 'sqlite':
return 'BLOB'
elif vendor == 'postgresql':
return 'BYTEA'
return 'BINARY(16)'
def from_db_value(self, value, expression, connection):
if value is None:
return None
if isinstance(value, UUIDObj):
return value
return UUIDObj(value)
def get_prep_value(self, value):
if value is None:
return None
if isinstance(value, UUIDObj):
return value.bytes
if isinstance(value, str):
try:
return _uuid.UUID(value).bytes
except ValueError:
return None
if isinstance(value, _uuid.UUID):
return value.bytes
if isinstance(value, bytes):
return value
return None
def get_prep_lookup(self, lookup_type, value):
if lookup_type in ('exact', 'iexact', 'in'):
if value is None:
return None
if lookup_type == 'in' and isinstance(value, (list, tuple)):
return [self.get_prep_value(v) for v in value]
return self.get_prep_value(value)
return super().get_prep_lookup(lookup_type, value)
def value_to_string(self, obj):
value = self.value_from_object(obj)
if isinstance(value, UUIDObj):
return value.str or ''
if isinstance(value, str):
return value
if isinstance(value, bytes) and len(value) == 16:
return str(_uuid.UUID(bytes=value))
if isinstance(value, _uuid.UUID):
return str(value)
return ''
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
del kwargs['max_length']
return name, path, args, kwargs
# === QModel 基础模型 ===
# === 用户管理器 ===
class UserManager(BaseUserManager):
"""用户管理器——提供用户创建与超级用户创建。
方法名保留蛇形命名以兼容 Django 内部约定。"""
def create_user(self, username, password=None, email='', **extra_fields):
"""创建普通用户"""
if not username:
raise ValueError('The Username field must be set')
user = self.model(
UserUUID=_uuid.uuid4().bytes,
UserName=username,
UserEmail=email or '',
**extra_fields
)
if password:
user.SetPassword(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password=None, **extra_fields):
"""创建超级用户"""
extra_fields.setdefault('UserAccountLicense', 1)
extra_fields.setdefault('UserPositionStatus', 0)
extra_fields.setdefault('IsStaff', True)
extra_fields.setdefault('IsSuperuser', True)
return self.create_user(username, password, **extra_fields)
# === 用户表 ===
class User(QModel):
"""用户表——系统核心身份模型(映射 SAAS 主库 User 表)"""
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID')
UserName = models.CharField(max_length=256, unique=True, db_column='UserName', verbose_name='用户名')
UserPassword = models.CharField(max_length=60, default='', db_column='UserPassword', verbose_name='密码')
UserPositionStatus = models.SmallIntegerField(default=0, db_column='UserPositionStatus', verbose_name='职位状态')
UserPersonalSignature = models.CharField(max_length=1024, default='', db_column='UserPersonalSignature', verbose_name='个人签名')
UserEmail = models.CharField(max_length=256, default='', db_column='UserEmail', verbose_name='邮箱')
UserEmailVerify = models.SmallIntegerField(default=0, db_column='UserEmailVerify', verbose_name='邮箱验证')
UserAccountLicense = models.SmallIntegerField(default=1, db_column='UserAccountLicense', verbose_name='账户许可')
UserGender = models.SmallIntegerField(default=-1, db_column='UserGender', verbose_name='性别')
UserTele = models.CharField(max_length=16, null=True, blank=True, db_column='UserTele', verbose_name='电话')
UserCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserCreateTime', verbose_name='创建时间')
UserLastLoginDate = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserLastLoginDate', verbose_name='最后登录')
IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工')
IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户')
objects = UserManager()
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'User'
verbose_name = '用户'
verbose_name_plural = '用户'
def __str__(self):
return self.UserName
@property
def UUIDStr(self):
"""返回 UUID 字符串形式"""
return UUIDObj(self.UserUUID).str
@property
def IsActive(self):
"""账户是否启用UserAccountLicense == 1"""
return self.UserAccountLicense == 1
@property
def IsOnJob(self):
"""是否在职UserPositionStatus == 0"""
return self.UserPositionStatus == 0
@property
def IsAnonymous(self):
return False
@property
def IsAuthenticated(self):
return True
@property
def PK(self):
return UUIDObj(self.UserUUID)
@property
def ID(self):
return UUIDObj(self.UserUUID)
def SetPassword(self, RawPassword):
"""设置密码——bcrypt 哈希"""
import bcrypt
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(RawPassword.encode('utf-8'), salt)
self.UserPassword = hashed.decode('utf-8')
def CheckPassword(self, RawPassword):
"""验证密码"""
import bcrypt
try:
return bcrypt.checkpw(
RawPassword.encode('utf-8'),
self.UserPassword.encode('utf-8')
)
except Exception:
return False
def GetTele(self):
"""获取电话,空则返回默认文本"""
return self.UserTele or '未填写'
def GetCompanies(self):
"""获取用户所属公司列表"""
# Company 模型不在 gvsdsdk 中,此处返回空 QuerySet
# 如需使用,请在子服务器中自行实现
return []
def GetRoles(self):
"""获取用户角色列表(需在同一 database 连接下使用)"""
user_role_uuids = UserRole.objects.filter(
UserUUID=self.UserUUID
).values_list('RoleUUID', flat=True)
return Role.objects.filter(RoleUUID__in=user_role_uuids)
def GetDepartments(self):
"""获取用户部门列表"""
# Department 模型不在 gvsdsdk 中,此处返回空列表
# 如需使用,请在子服务器中自行实现
return []
def HasPermission(self, PermCode):
"""检查用户是否拥有指定权限(需在同一 database 连接下使用)
通过 UserRole → RolePermission → Permission 链路查询,
汇总所有角色的操作权限后判断。
"""
user_role_uuids = UserRole.objects.filter(
UserUUID=self.UserUUID
).values_list('RoleUUID', flat=True)
perm_uuids = RolePermission.objects.filter(
RoleUUID__in=user_role_uuids
).values_list('PermUUID', flat=True)
return Permission.objects.filter(
PermUUID__in=perm_uuids,
PermCode=PermCode,
PermStatus=1,
).exists()
# Django auth 框架兼容(内部使用蛇形命名)
@property
def is_active(self):
return self.IsActive
@property
def is_anonymous(self):
return self.IsAnonymous
@property
def is_authenticated(self):
return self.IsAuthenticated
@property
def pk(self):
return self.PK
@property
def id(self):
return self.ID
def set_password(self, raw_password):
self.SetPassword(raw_password)
def check_password(self, raw_password):
return self.CheckPassword(raw_password)
# === 用户头像表 ===
class UserAvatar(QModel):
"""用户头像表(映射 SAAS 主库 UserAvatar 表)"""
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID')
UserAvatar = models.BinaryField(editable=True, db_column='UserAvatar', verbose_name='头像数据')
AvatarUploadTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='AvatarUploadTime', verbose_name='上传时间')
AvatarFileType = models.CharField(max_length=16, default='png', db_column='AvatarFileType', verbose_name='文件类型')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'UserAvatar'
verbose_name = '用户头像'
verbose_name_plural = '用户头像'
# === 用户双因素认证表 ===
class User2FA(QModel):
"""用户双因素认证表(映射 SAAS 主库 User2FA 表)"""
User2FAUUID = UUIDField(primary_key=True, db_column='User2FAUUID', verbose_name='2FA UUID')
UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID')
SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥')
IsEnabled = models.SmallIntegerField(default=0, db_column='IsEnabled', verbose_name='是否启用')
Verified = models.SmallIntegerField(default=0, db_column='Verified', verbose_name='已验证')
LastUsedAt = models.DateTimeField(null=True, blank=True, db_column='LastUsedAt', verbose_name='最后使用')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'User2FA'
verbose_name = '双因素认证'
verbose_name_plural = '双因素认证'
# === 双因素认证待验证表 ===
class UserPending2FA(QModel):
"""双因素认证待验证表(映射 SAAS 主库 UserPending2FA 表)"""
PendingUUID = UUIDField(primary_key=True, db_column='PendingUUID', verbose_name='待确认UUID')
UserUUID = UUIDField(unique=True, db_column='UserUUID', verbose_name='用户UUID')
SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥')
ExpireAt = models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'UserPending2FA'
verbose_name = '2FA待确认'
verbose_name_plural = '2FA待确认'
# === 临时令牌表 ===
def _EltEndTimeDefault():
from django.conf import settings
days = getattr(settings, 'ELT_TOKEN_LIFETIME_DAYS', 30)
return _datetime.datetime.utcnow() + _datetime.timedelta(days=days)
class EphemeralToken(QModel):
"""临时令牌表——用于 API 认证与会话管理(映射 SAAS 主库 EphemeralToken 表)"""
ELTUUID = UUIDField(primary_key=True, db_column='ELTUUID', verbose_name='令牌UUID')
ELTUserUUID = UUIDField(db_index=True, db_column='ELTUserUUID', verbose_name='用户UUID')
ELTCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='ELTCreateTime', verbose_name='创建时间')
ELTEndTime = models.DateTimeField(default=_EltEndTimeDefault, db_column='ELTEndTime', verbose_name='过期时间')
ELTType = models.SmallIntegerField(default=0, db_column='ELTType', verbose_name='令牌类型')
ELTAllowIP = models.JSONField(default=list, db_column='ELTAllowIP', verbose_name='允许IP')
ELTStatus = models.SmallIntegerField(default=1, db_index=True, db_column='ELTStatus', verbose_name='状态')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'EphemeralToken'
verbose_name = '临时令牌'
verbose_name_plural = '临时令牌'
def IsExpired(self):
"""检查令牌是否过期"""
return _datetime.datetime.utcnow() > self.ELTEndTime.replace(tzinfo=None)
def IsValid(self):
"""检查令牌是否有效"""
return self.ELTStatus == 1 and not self.IsExpired()
# === 用户登录日志表 ===
class UserLoginLog(QModel):
"""用户登录日志表(映射 SAAS 主库 UserLoginLog 表)"""
LogUUID = UUIDField(primary_key=True, db_column='LogUUID', verbose_name='日志UUID')
UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID')
LoginIP = models.CharField(max_length=39, db_column='LoginIP', verbose_name='登录IP')
LoginPlatform = models.CharField(max_length=32, db_column='LoginPlatform', verbose_name='登录平台')
LoginStatus = models.SmallIntegerField(db_column='LoginStatus', verbose_name='登录状态')
LoginTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='LoginTime', verbose_name='登录时间')
UserAgent = models.CharField(max_length=512, default='', db_column='UserAgent', verbose_name='用户代理')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'UserLoginLog'
verbose_name = '登录日志'
verbose_name_plural = '登录日志'
# === 账户激活码表 ===
class AccountActivation(QModel):
"""账户激活码表(映射 SAAS 主库 AccountActivation 表)"""
AccountActivationCode = FixedBinaryField(primary_key=True, binary_length=6, db_column='AccountActivationCode', verbose_name='激活码')
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'AccountActivation'
verbose_name = '账户激活'
verbose_name_plural = '账户激活'
# === 角色表 ===
class Role(QModel):
"""角色表——权限分配单元(映射 SAAS 主库 Role 表)
角色是真正承载操作权限的实体,在单个租户内部独立存在,
不依附任何公司或部门。通过 AssignScope 控制角色的分配可见范围:
- 0全租户管理员可分发
- 1仅指定公司管理员可分发AssignScopeUUID = CompanyUUID
- 2仅指定部门管理员可分发AssignScopeUUID = DeptUUID
"""
RoleUUID = UUIDField(primary_key=True, db_column='RoleUUID', verbose_name='角色UUID')
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
RoleName = models.CharField(max_length=128, db_column='RoleName', verbose_name='角色名称')
RoleType = models.SmallIntegerField(default=2, db_column='RoleType', verbose_name='角色类型')
RoleStatus = models.SmallIntegerField(default=1, db_column='RoleStatus', verbose_name='角色状态')
AssignScope = models.SmallIntegerField(default=0, db_column='AssignScope', verbose_name='分配可见范围')
AssignScopeUUID = UUIDField(null=True, blank=True, db_column='AssignScopeUUID', verbose_name='分配范围UUID')
RoleDesc = models.CharField(max_length=512, default='', db_column='RoleDesc', verbose_name='角色描述')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'Role'
verbose_name = '角色'
verbose_name_plural = '角色'
def __str__(self):
return self.RoleName
def GetPermissions(self):
"""获取角色拥有的权限列表(通过 RolePermission 中间表)"""
perm_uuids = RolePermission.objects.filter(
RoleUUID=self.RoleUUID
).values_list('PermUUID', flat=True)
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
def GetUsers(self):
"""获取拥有该角色的用户列表"""
user_uuids = UserRole.objects.filter(
RoleUUID=self.RoleUUID
).values_list('UserUUID', flat=True)
return User.objects.filter(UserUUID__in=user_uuids)
# === 权限表 ===
class Permission(QModel):
"""权限表——操作权限定义(映射 SAAS 主库 Permission 表)
独立的权限定义表,租户内全局可用,不再挂在角色下。
通过 RolePermission 中间表与角色建立多对多关系,
通过 PositionTemplatePermission 中间表与岗位模板建立多对多关系。
"""
PermUUID = UUIDField(primary_key=True, db_column='PermUUID', verbose_name='权限UUID')
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
PermName = models.CharField(max_length=128, db_column='PermName', verbose_name='权限名称')
PermCode = models.CharField(max_length=128, db_column='PermCode', verbose_name='权限键')
PermCategory = models.CharField(max_length=64, default='', db_column='PermCategory', verbose_name='权限分类')
PermStatus = models.SmallIntegerField(default=1, db_column='PermStatus', verbose_name='是否启用')
PermDesc = models.CharField(max_length=512, default='', db_column='PermDesc', verbose_name='描述')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'Permission'
verbose_name = '权限'
verbose_name_plural = '权限'
def __str__(self):
return f"{self.PermName} ({self.PermCode})"
def GetRoles(self):
"""获取拥有该权限的角色列表"""
role_uuids = RolePermission.objects.filter(
PermUUID=self.PermUUID
).values_list('RoleUUID', flat=True)
return Role.objects.filter(RoleUUID__in=role_uuids)
# === 用户角色关联表 ===
class UserRole(QModel):
"""用户角色关联表——用户与角色的多对多映射(映射 SAAS 主库 UserRole 表)
通过 CompanyUUID 标识该用户-角色关联所属的公司,
用于菜单按公司差异化显示、数据权限按公司过滤等场景。
同一用户在不同公司下可绑定不同角色。
注意UserUUID 和 RoleUUID 在 gvsds 中是 ForeignKey
但此处改为 UUIDField 以保持独立性。
"""
UserRoleUUID = UUIDField(primary_key=True, db_column='UserRoleUUID', verbose_name='关联UUID')
# 原 gvsds 中为 ForeignKey(User),此处改为 UUIDField
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
# 原 gvsds 中为 ForeignKey('Role'),此处改为 UUIDField
RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID')
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'UserRole'
verbose_name = '用户角色关联'
verbose_name_plural = '用户角色关联'
# === 角色权限关联表 ===
class RolePermission(QModel):
"""角色权限关联表——角色与操作权限的多对多映射(映射 SAAS 主库 RolePermission 表)
一个角色可以拥有多个权限,一个权限也可以被多个角色引用。
角色和具体操作权限为多对多绑定关系,一套权限集合可以打包在一个角色内统一维护。
"""
RolePermUUID = UUIDField(primary_key=True, db_column='RolePermUUID', verbose_name='关联UUID')
RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID')
PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'RolePermission'
verbose_name = '角色权限关联'
verbose_name_plural = '角色权限关联'
# === 用户 ABAC 表 ===
class UserABAC(QModel):
"""用户 ABAC 表——基于属性的访问控制(映射 SAAS 主库 UserABAC 表)"""
UserABACUUID = UUIDField(primary_key=True, db_column='UserABACUUID', verbose_name='ABAC UUID')
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
HierarchicalPermissionIdentifiers = models.CharField(max_length=256, db_column='HierarchicalPermissionIdentifiers', verbose_name='层级权限标识')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'UserABAC'
verbose_name = '用户ABAC'
verbose_name_plural = '用户ABAC'
# === 密码表 ===
class Password(QModel):
"""密码表——独立密码存储(映射 SAAS 主库 Password 表)"""
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID')
Password = models.CharField(max_length=1024, db_column='Password', verbose_name='密码哈希')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'Password'
verbose_name = '密码'
verbose_name_plural = '密码'
# === 商铺表 ===
class Shop(QModel):
"""商铺表——SAAS 多商铺体系中的商铺实体
每个商铺属于一个公司CompanyUUID可关联多个角色通过 RoleShop
"""
ShopUUID = UUIDField(primary_key=True, db_column='ShopUUID', verbose_name='商铺UUID')
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
ShopName = models.CharField(max_length=128, db_column='ShopName', verbose_name='商铺名称')
ShopCode = models.CharField(max_length=64, default='', db_column='ShopCode', verbose_name='商铺编码')
ShopType = models.SmallIntegerField(default=0, db_column='ShopType', verbose_name='商铺类型')
ShopStatus = models.SmallIntegerField(default=1, db_column='ShopStatus', verbose_name='商铺状态')
ShopAddress = models.CharField(max_length=512, default='', db_column='ShopAddress', verbose_name='商铺地址')
ShopContact = models.CharField(max_length=128, default='', db_column='ShopContact', verbose_name='联系人')
ShopPhone = models.CharField(max_length=32, default='', db_column='ShopPhone', verbose_name='联系电话')
ShopDesc = models.CharField(max_length=512, default='', db_column='ShopDesc', verbose_name='描述')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'Shop'
verbose_name = '商铺'
verbose_name_plural = '商铺'
def __str__(self):
return self.ShopName
def GetRoles(self):
"""获取商铺关联的所有角色"""
role_uuids = RoleShop.objects.filter(
ShopUUID=self.ShopUUID
).values_list('RoleUUID', flat=True)
return Role.objects.filter(RoleUUID__in=role_uuids)
# === 角色商铺关联表 ===
class RoleShop(QModel):
"""角色商铺关联表——角色与商铺的多对多映射
一个角色可以管理多个商铺,一个商铺也可以被多个角色管理。
"""
RoleShopUUID = UUIDField(primary_key=True, db_column='RoleShopUUID', verbose_name='关联UUID')
RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID')
ShopUUID = UUIDField(db_column='ShopUUID', verbose_name='商铺UUID')
IsMainShop = models.SmallIntegerField(default=0, db_column='IsMainShop', verbose_name='主商铺')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'RoleShop'
verbose_name = '角色商铺关联'
verbose_name_plural = '角色商铺关联'
# === 岗位模板表 ===
class PositionTemplate(QModel):
"""岗位模板表——权限初始化快照模板(映射 SAAS 主库 PositionTemplate 表)
租户内全局可重复使用,定位是权限初始化快照模板,
不直接分配给员工、不参与运行时鉴权。
新建角色时可一键复制模板内的全部权限内容生成新角色。
模板后续调整只会作用于之后新创建的角色,
已有角色不会同步变更,实现模板复用和存量权限独立管控互不干扰。
"""
PositionTemplateUUID = UUIDField(primary_key=True, db_column='PositionTemplateUUID', verbose_name='岗位模板UUID')
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
TemplateName = models.CharField(max_length=128, db_column='TemplateName', verbose_name='模板名称')
TemplateCode = models.CharField(max_length=64, default='', db_column='TemplateCode', verbose_name='模板编码')
TemplateStatus = models.SmallIntegerField(default=1, db_column='TemplateStatus', verbose_name='模板状态')
TemplateDesc = models.CharField(max_length=512, default='', db_column='TemplateDesc', verbose_name='模板描述')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'PositionTemplate'
verbose_name = '岗位模板'
verbose_name_plural = '岗位模板'
def __str__(self):
return self.TemplateName
def GetPermissions(self):
"""获取模板关联的权限列表"""
perm_uuids = PositionTemplatePermission.objects.filter(
PositionTemplateUUID=self.PositionTemplateUUID
).values_list('PermUUID', flat=True)
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
def CopyToRole(self, RoleName, TenantUUID):
"""从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)"""
role = Role.objects.create(
RoleUUID=_uuid.uuid4().bytes,
TenantUUID=TenantUUID,
RoleName=RoleName,
)
template_perms = PositionTemplatePermission.objects.filter(
PositionTemplateUUID=self.PositionTemplateUUID
)
for tp in template_perms:
RolePermission.objects.create(
RolePermUUID=_uuid.uuid4().bytes,
RoleUUID=role.RoleUUID,
PermUUID=tp.PermUUID,
)
return role
# === 岗位模板权限关联表 ===
class PositionTemplatePermission(QModel):
"""岗位模板权限关联表——岗位模板与操作权限的多对多映射
仅作为复制源头,不参与运行时鉴权。
新建角色时一键复制模板权限到角色,复制后模板与角色无运行时绑定。
"""
PTPermUUID = UUIDField(primary_key=True, db_column='PTPermUUID', verbose_name='关联UUID')
PositionTemplateUUID = UUIDField(db_column='PositionTemplateUUID', verbose_name='岗位模板UUID')
PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'PositionTemplatePermission'
verbose_name = '岗位模板权限关联'
verbose_name_plural = '岗位模板权限关联'
# === 人事职务标签表 ===
class Position(QModel):
"""人事职务标签表——仅用于人事管理标注(映射 SAAS 主库 Position 表)
员工会标注对应的职务标签,该标签仅为人事标注信息,
不直接绑定权限,和角色、权限体系完全解耦。
人员调岗、升职只修改组织归属和人事职务标签,权限只需增删绑定角色即可,互不干扰。
"""
PositionUUID = UUIDField(primary_key=True, db_column='PositionUUID', verbose_name='职务UUID')
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
PositionName = models.CharField(max_length=128, db_column='PositionName', verbose_name='职务名称')
PositionCode = models.CharField(max_length=64, default='', db_column='PositionCode', verbose_name='职务编码')
PositionLevel = models.SmallIntegerField(default=0, db_column='PositionLevel', verbose_name='职务级别')
PositionStatus = models.SmallIntegerField(default=1, db_column='PositionStatus', verbose_name='状态')
PositionDesc = models.CharField(max_length=512, default='', db_column='PositionDesc', verbose_name='描述')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'Position'
verbose_name = '人事职务标签'
verbose_name_plural = '人事职务标签'
def __str__(self):
return self.PositionName
# === 员工部门关联表 ===
class UserDept(QModel):
"""员工部门关联表——员工与部门的多对多映射(映射 SAAS 主库 UserDept 表)
支持一人多部门兼职,同时标注该员工在对应部门的职务标签。
该层级仅用于组织架构展示、审批流转、人员统计,
同时为数据权限判断提供人员所属组织属性依据,不参与操作权限校验。
"""
UserDeptUUID = UUIDField(primary_key=True, db_column='UserDeptUUID', verbose_name='关联UUID')
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
DeptUUID = UUIDField(db_column='DeptUUID', verbose_name='部门UUID')
PositionUUID = UUIDField(null=True, blank=True, db_column='PositionUUID', verbose_name='职务标签UUID')
IsMainDept = models.SmallIntegerField(default=1, db_column='IsMainDept', verbose_name='是否主部门')
CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间')
class Meta:
managed = False
app_label = 'gvsdsdk'
db_table = 'UserDept'
verbose_name = '员工部门关联'
verbose_name_plural = '员工部门关联'