UserIDCard 原 db_table 为 UserIDCard,实际表对应 gerp.UserIDCard.db_table='IDCard'(lower_case_table_names=1 下解析为 idcard),导致 SDK 查询直接报 1146;同时补齐镜像缺失的 Appointments / ReadmeReport。校验差异 51 → 13(余下均为本地库缺表)。
1769 lines
94 KiB
Python
1769 lines
94 KiB
Python
"""GVSDS SDK 数据模型模块
|
||
|
||
将 SAAS 主服务器的用户表(User)、权限表(Permission)、角色表(Role)、
|
||
用户角色关联表(UserRole)、角色权限关联表(RolePermission)、
|
||
岗位模板表(PositionTemplate)、岗位模板权限关联表(PositionTemplatePermission)、
|
||
人事职务标签表(Position)封装为独立的 Django ORM 模型,
|
||
供子服务器直接进行跨服务器 ORM 查询。
|
||
|
||
权限体系四大分层:
|
||
1. 组织人事层:Tenant → Company → Department,员工通过 UserRoleDepartment/UserRoleWorkgroup 关联表归属部门/工作组
|
||
2. 岗位模板层:PositionTemplate + PositionTemplatePermission,权限初始化快照
|
||
3. 角色权限载体层:Role + RolePermission,运行时鉴权实体
|
||
4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性)
|
||
|
||
使用方式(子服务器):
|
||
from gvsdsdk.models import Permission, Role, UserRole, RolePermission
|
||
|
||
# 查询所有启用的用户(User 为抽象基类,需经 _GetUserModel 获取具体模型)
|
||
from gvsdsdk.model_utils import _GetUserModel
|
||
UserModel = _GetUserModel()
|
||
users = UserModel.objects.filter(UserAccountLicense=1)
|
||
|
||
# 或配置 DatabaseRouter 后直接使用
|
||
users = UserModel.objects.filter(UserAccountLicense=1)
|
||
|
||
设计原则:
|
||
1. 独立:不依赖 gvsds 内部模块,可在任意 Django 项目中独立使用
|
||
2. 非托管:managed=False,不创建/迁移表,仅映射已有表结构
|
||
3. PascalCase:字段名、方法名遵循帕斯卡命名法
|
||
4. 兼容:db_table、db_column 与 SAAS 主库完全一致
|
||
5. 租户隔离:所有权限相关数据依附于独立租户空间,不同租户互相不可见
|
||
|
||
注意:User 为抽象基类,仅包含 SAAS 主库标准字段。
|
||
业务子类应继承 User 并添加业务字段与方法(如 gvsds.apps.core.models.User)。
|
||
"""
|
||
|
||
import uuid as _uuid
|
||
import bcrypt
|
||
from django.db import models, transaction
|
||
from django.contrib.auth.models import BaseUserManager
|
||
from django.utils import timezone as _timezone
|
||
from gvsdsdk.model_base import QModel
|
||
from gvsdsdk.model_utils import UUIDObj, _GetUserModel
|
||
|
||
_BCRYPT_ROUNDS = 12 # bcrypt 计算成本因子,与 auth/crypto.py 保持一致
|
||
_BCRYPT_HASH_LEN = 60 # bcrypt 哈希固定长度
|
||
|
||
|
||
def _UtcNow():
|
||
"""获取当前 UTC 时间(兼容 Django timezone.now())"""
|
||
return _timezone.now()
|
||
|
||
|
||
# === 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('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('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 as e:
|
||
raise ValueError(f'无效的 UUID 字符串: {value!r}') from e
|
||
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()
|
||
kwargs.pop('max_length', None)
|
||
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 表标准字段)
|
||
|
||
本类为抽象基类,仅包含 SAAS 主库 User 表的标准字段和方法。
|
||
业务子类应继承本类并添加业务字段与方法(如 gvsds.apps.core.models.User)。
|
||
"""
|
||
|
||
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=_uuid.uuid4)
|
||
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=_UtcNow, db_column='UserCreateTime', verbose_name='创建时间')
|
||
UserLastLoginDate = models.DateTimeField(default=_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='是否超级用户')
|
||
|
||
# Django auth 框架必需(由具体子类覆盖)
|
||
USERNAME_FIELD = 'UserName'
|
||
REQUIRED_FIELDS = []
|
||
|
||
class Meta:
|
||
abstract = True
|
||
|
||
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 哈希"""
|
||
salt = bcrypt.gensalt(rounds=_BCRYPT_ROUNDS)
|
||
hashed = bcrypt.hashpw(RawPassword.encode('utf-8'), salt)
|
||
self.UserPassword = hashed.decode('utf-8')
|
||
|
||
def CheckPassword(self, RawPassword):
|
||
"""验证密码——兼容 bcrypt 哈希和明文存储(明文仅用于旧数据迁移期)"""
|
||
import hmac as _hmac
|
||
import logging as _logging
|
||
stored = self.UserPassword or ''
|
||
# bcrypt 哈希以 $2b$ 或 $2a$ 开头,长度固定 60
|
||
if stored.startswith(('$2b$', '$2a$')) and len(stored) == _BCRYPT_HASH_LEN:
|
||
try:
|
||
return bcrypt.checkpw(
|
||
RawPassword.encode('utf-8'),
|
||
stored.encode('utf-8')
|
||
)
|
||
except (ValueError, TypeError):
|
||
return False
|
||
else:
|
||
# 明文密码(旧数据兼容)——记录警告,建议尽快迁移为 bcrypt 哈希
|
||
# 生产环境可通过 settings.GVSDSDK_DISALLOW_PLAINTEXT_PASSWORD=True 关闭明文密码验证
|
||
from django.conf import settings
|
||
if getattr(settings, 'GVSDSDK_DISALLOW_PLAINTEXT_PASSWORD', False):
|
||
_logging.getLogger(__name__).warning(
|
||
'User %s 密码仍为明文存储,生产环境已禁用明文密码验证',
|
||
getattr(self, 'UserUUID', 'unknown'),
|
||
)
|
||
return False
|
||
_logging.getLogger(__name__).warning(
|
||
'User %s 密码仍为明文存储,请尽快通过 hash_passwords 命令迁移为 bcrypt 哈希',
|
||
getattr(self, 'UserUUID', 'unknown'),
|
||
)
|
||
return _hmac.compare_digest(stored, RawPassword)
|
||
|
||
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=_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=_UtcNow, 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待确认'
|
||
|
||
|
||
# === 用户登录日志表 ===
|
||
|
||
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=_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=_UtcNow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'AccountActivation'
|
||
verbose_name = '账户激活'
|
||
verbose_name_plural = '账户激活'
|
||
|
||
|
||
# === 租户表 ===
|
||
|
||
class Tenant(QModel):
|
||
"""租户表——多租户架构核心(映射 SAAS 主库 Tenant 表)"""
|
||
|
||
TenantUUID = UUIDField(primary_key=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
TenantName = models.CharField(max_length=256, db_column='TenantName', verbose_name='租户名称')
|
||
TenantStatus = models.SmallIntegerField(default=1, db_column='TenantStatus', verbose_name='租户状态')
|
||
TenantExpireTime = models.DateTimeField(db_column='TenantExpireTime', verbose_name='到期时间')
|
||
ReviewStatus = models.SmallIntegerField(default=0, db_column='ReviewStatus', verbose_name='审核状态')
|
||
ReviewRemark = models.CharField(max_length=512, default='', db_column='ReviewRemark', verbose_name='审核备注')
|
||
ReviewerUUID = UUIDField(null=True, blank=True, db_column='ReviewerUUID', verbose_name='审核人UUID')
|
||
ReviewTime = models.DateTimeField(null=True, blank=True, db_column='ReviewTime', verbose_name='审核时间')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Tenant'
|
||
verbose_name = '租户'
|
||
verbose_name_plural = '租户'
|
||
|
||
def __str__(self):
|
||
return self.TenantName
|
||
|
||
|
||
# === 公司/部门表 ===
|
||
|
||
class Company(QModel):
|
||
"""公司表——系统内组织机构(映射 SAAS 主库 Company 表)"""
|
||
|
||
CompanyUUID = UUIDField(primary_key=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
|
||
ParentCompanyUUID = UUIDField(null=True, blank=True, db_column='ParentCompanyUUID', verbose_name='上级公司UUID')
|
||
CompanyName = models.CharField(max_length=256, db_column='CompanyName', verbose_name='公司全称')
|
||
CompanyShort = models.CharField(max_length=100, db_column='CompanyShort', verbose_name='公司简称')
|
||
CompanyIcon = models.CharField(max_length=512, null=True, blank=True, db_column='CompanyIcon', verbose_name='公司图标')
|
||
CompanyStatus = models.SmallIntegerField(default=1, db_column='CompanyStatus', verbose_name='公司状态')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Company'
|
||
verbose_name = '公司'
|
||
verbose_name_plural = '公司'
|
||
|
||
def __str__(self):
|
||
return self.CompanyName
|
||
|
||
|
||
class Department(QModel):
|
||
"""部门表——组织架构层级(映射 SAAS 主库 Dept 表)"""
|
||
|
||
DeptUUID = UUIDField(primary_key=True, db_column='DeptUUID', verbose_name='部门UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
ParentDeptUUID = UUIDField(null=True, blank=True, db_column='ParentDeptUUID', verbose_name='上级部门UUID')
|
||
DeptName = models.CharField(max_length=256, db_column='DeptName', verbose_name='部门名称')
|
||
DeptSort = models.IntegerField(default=0, db_column='DeptSort', verbose_name='排序')
|
||
DeptStatus = models.SmallIntegerField(default=1, db_column='DeptStatus', verbose_name='部门状态')
|
||
PositionCategoryUUID = UUIDField(null=True, blank=True, db_column='PositionCategoryUUID', verbose_name='绑定通用部门UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Dept'
|
||
verbose_name = '部门'
|
||
verbose_name_plural = '部门'
|
||
|
||
def __str__(self):
|
||
return self.DeptName
|
||
|
||
|
||
class UserRoleDepartment(QModel):
|
||
"""用户-角色对 与 部门 的多对多关联表(映射 SAAS 主库 UserRoleDepartment 表)"""
|
||
|
||
UserRoleDepartmentUUID = UUIDField(primary_key=True, db_column='UserRoleDepartmentUUID', verbose_name='关联UUID')
|
||
UserRoleUUID = UUIDField(db_column='UserRoleUUID', verbose_name='用户-角色关联UUID')
|
||
DeptUUID = UUIDField(db_column='DeptUUID', verbose_name='部门UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
# 注意:PositionUUID 已于 gerp 0034 迁移多值化(改存 UserRoleDepartmentPosition 子表),数据库已删列,
|
||
# 此处严禁再加回字段,否则 SDK 查询会报 1054 Unknown column
|
||
RoleType = models.SmallIntegerField(default=0, db_column='RoleType', verbose_name='角色类型')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'UserRoleDepartment'
|
||
verbose_name = '用户-角色部门关联'
|
||
verbose_name_plural = '用户-角色部门关联'
|
||
|
||
|
||
# === 用户-角色工作组关联表 ===
|
||
|
||
class UserRoleWorkgroup(QModel):
|
||
"""用户-角色对 与 工作组 的多对多关联表(映射 SAAS 主库 UserRoleWorkgroup 表)"""
|
||
|
||
UserRoleWorkgroupUUID = UUIDField(primary_key=True, db_column='UserRoleWorkgroupUUID', verbose_name='关联UUID')
|
||
UserRoleUUID = UUIDField(db_column='UserRoleUUID', verbose_name='用户-角色关联UUID')
|
||
WorkgroupUUID = UUIDField(db_column='WorkgroupUUID', verbose_name='工作组UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
# 职务不再存于本表:工作组指向公司的员工关系,职务复用 UserRoleDepartmentPosition 多值子表
|
||
RoleType = models.SmallIntegerField(default=0, db_column='RoleType', verbose_name='角色类型')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'UserRoleWorkgroup'
|
||
verbose_name = '用户-角色工作组关联'
|
||
verbose_name_plural = '用户-角色工作组关联'
|
||
|
||
|
||
# === 用户-角色-部门 职务多对多子表 ===
|
||
|
||
class UserRoleDepartmentPosition(QModel):
|
||
"""用户-角色对 与 部门关联 的职务多对多子表(映射 SAAS 主库 UserRoleDepartmentPosition 表)
|
||
|
||
一个 (用户↔角色)↔部门 关系可身兼数职:职务存放在本子表,
|
||
UserRoleDepartment 表自身不保留单值 PositionUUID 列。
|
||
工作组成员复用公司员工关系:职务同样落在本表(主部门关联下)。
|
||
"""
|
||
|
||
UserRoleDepartmentPositionUUID = UUIDField(primary_key=True, db_column='UserRoleDepartmentPositionUUID', verbose_name='关联职务UUID')
|
||
UserRoleDepartmentUUID = UUIDField(db_column='UserRoleDepartmentUUID', verbose_name='用户角色部门关联UUID')
|
||
PositionUUID = UUIDField(db_column='PositionUUID', verbose_name='职务UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'UserRoleDepartmentPosition'
|
||
unique_together = (('UserRoleDepartmentUUID', 'PositionUUID'),)
|
||
verbose_name = '部门职务关联'
|
||
verbose_name_plural = '部门职务关联'
|
||
|
||
|
||
# === 消息表 ===
|
||
|
||
class Message(QModel):
|
||
"""消息表——系统通知与站内信(映射 SAAS 主库 Message 表)"""
|
||
|
||
MessageUUID = UUIDField(primary_key=True, db_column='MessageUUID', verbose_name='消息UUID')
|
||
SenderUUID = UUIDField(null=True, blank=True, db_column='SenderUUID', verbose_name='发送者UUID')
|
||
ReceiverUUID = UUIDField(db_index=True, db_column='ReceiverUUID', verbose_name='接收者UUID')
|
||
Title = models.CharField(max_length=256, db_column='Title', verbose_name='标题')
|
||
Content = models.TextField(default='', db_column='Content', verbose_name='内容')
|
||
MsgType = models.SmallIntegerField(default=0, db_column='MsgType', verbose_name='消息类型')
|
||
IsRead = models.SmallIntegerField(default=0, db_column='IsRead', verbose_name='已读')
|
||
RelatedUUID = models.CharField(max_length=64, default='', db_column='RelatedUUID', verbose_name='关联UUID')
|
||
RelatedType = models.CharField(max_length=32, default='', db_column='RelatedType', verbose_name='关联类型')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Message'
|
||
verbose_name = '消息'
|
||
verbose_name_plural = '消息'
|
||
|
||
def __str__(self):
|
||
return self.Title
|
||
|
||
|
||
# === 个人中心表 ===
|
||
|
||
class UserIDCard(QModel):
|
||
"""用户身份证表——实名信息(映射 SAAS 主库 IDCard 表;表名见 gerp.UserIDCard.db_table)"""
|
||
|
||
UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID')
|
||
Name = models.CharField(max_length=128, db_column='Name', verbose_name='姓名')
|
||
OldName = models.CharField(max_length=128, default='', db_column='OldName', verbose_name='曾用名')
|
||
Born = models.DateField(db_column='Born', verbose_name='出生日期')
|
||
Sex = models.CharField(max_length=10, db_column='Sex', verbose_name='性别')
|
||
Ethnic = models.CharField(max_length=20, db_column='Ethnic', verbose_name='民族')
|
||
IdentificationNumber = models.CharField(max_length=20, db_column='IdentificationNumber', verbose_name='身份证号')
|
||
StudentRegistrationNumber = models.CharField(max_length=30, default='', db_column='StudentRegistrationNumber', verbose_name='学籍号')
|
||
ChangeStudentStatus = models.JSONField(default=list, db_column='ChangeStudentStatus', verbose_name='学籍变动')
|
||
CorrespondenceAddress = models.JSONField(default=list, db_column='CorrespondenceAddress', verbose_name='通讯地址')
|
||
HeadImage = models.CharField(max_length=4096, default='', db_column='HeadImage', verbose_name='头像')
|
||
InformationAboutFamilyMembers = models.JSONField(default=dict, db_column='InformationAboutFamilyMembers', verbose_name='家庭成员信息')
|
||
Appointments = models.JSONField(default=dict, db_column='Appointments', verbose_name='任职信息')
|
||
ReadmeReport = models.TextField(default='', db_column='ReadmeReport', verbose_name='自述报告')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'IDCard'
|
||
verbose_name = '用户身份证'
|
||
verbose_name_plural = '用户身份证'
|
||
|
||
|
||
class PersonalPage(QModel):
|
||
"""个人主页表——用户展示页面(映射 SAAS 主库 PersonalPage 表)"""
|
||
|
||
PersonalPageUserUUID = UUIDField(primary_key=True, db_column='PersonalPageUserUUID', verbose_name='用户UUID')
|
||
PersonalPageName = models.CharField(max_length=256, unique=True, db_column='PersonalPageName', verbose_name='主页名称')
|
||
Subtitle = models.CharField(max_length=256, default='', db_column='Subtitle', verbose_name='副标题')
|
||
BackgroundLink = models.CharField(max_length=256, default='', db_column='BackgroundLink', verbose_name='背景链接')
|
||
Classification = models.CharField(max_length=256, default='', db_column='Classification', verbose_name='分类')
|
||
Github = models.CharField(max_length=256, default='', db_column='Github', verbose_name='GitHub')
|
||
Wechat = models.CharField(max_length=256, default='', db_column='Wechat', verbose_name='微信')
|
||
QQ = models.CharField(max_length=256, default='', db_column='Qq', verbose_name='QQ')
|
||
SelfIntroduction = models.TextField(default='', db_column='SelfIntroduction', verbose_name='自我介绍')
|
||
ProjectExperience = models.CharField(max_length=256, default='', db_column='ProjectExperience', verbose_name='项目经验')
|
||
SatisfiedCustomers = models.CharField(max_length=256, default='', db_column='SatisfiedCustomers', verbose_name='满意客户')
|
||
ReceiveAward = models.CharField(max_length=256, default='', db_column='ReceiveAward', verbose_name='获奖')
|
||
CoffeeCups = models.CharField(max_length=256, default='', db_column='CoffeeCups', verbose_name='咖啡杯数')
|
||
Achievement1Icon = models.CharField(max_length=256, default='', db_column='Achievement1Icon', verbose_name='成就1图标')
|
||
Achievement1Name = models.CharField(max_length=256, default='', db_column='Achievement1Name', verbose_name='成就1名称')
|
||
Achievement1Time = models.CharField(max_length=256, default='', db_column='Achievement1Time', verbose_name='成就1时间')
|
||
Achievement1Introduction = models.TextField(default='', db_column='Achievement1Introduction', verbose_name='成就1介绍')
|
||
Achievement2Icon = models.CharField(max_length=256, default='', db_column='Achievement2Icon', verbose_name='成就2图标')
|
||
Achievement2Name = models.CharField(max_length=256, default='', db_column='Achievement2Name', verbose_name='成就2名称')
|
||
Achievement2Time = models.CharField(max_length=256, default='', db_column='Achievement2Time', verbose_name='成就2时间')
|
||
Achievement2Introduction = models.TextField(default='', db_column='Achievement2Introduction', verbose_name='成就2介绍')
|
||
Achievement3Icon = models.CharField(max_length=256, default='', db_column='Achievement3Icon', verbose_name='成就3图标')
|
||
Achievement3Name = models.CharField(max_length=256, default='', db_column='Achievement3Name', verbose_name='成就3名称')
|
||
Achievement3Time = models.CharField(max_length=256, default='', db_column='Achievement3Time', verbose_name='成就3时间')
|
||
Achievement3Introduction = models.TextField(default='', db_column='Achievement3Introduction', verbose_name='成就3介绍')
|
||
Achievement4Icon = models.CharField(max_length=256, default='', db_column='Achievement4Icon', verbose_name='成就4图标')
|
||
Achievement4Name = models.CharField(max_length=256, default='', db_column='Achievement4Name', verbose_name='成就4名称')
|
||
Achievement4Time = models.CharField(max_length=256, default='', db_column='Achievement4Time', verbose_name='成就4时间')
|
||
Achievement4Introduction = models.TextField(default='', db_column='Achievement4Introduction', verbose_name='成就4介绍')
|
||
Tags = models.CharField(max_length=256, default='', db_column='Tags', verbose_name='标签')
|
||
Markdown = models.TextField(default='', db_column='Markdown', verbose_name='Markdown')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'PersonalPage'
|
||
verbose_name = '个人主页'
|
||
verbose_name_plural = '个人主页'
|
||
|
||
|
||
class UserSettings(QModel):
|
||
"""用户设置表——存储每位用户的个性化系统设置(映射 SAAS 主库 UserSettings 表)"""
|
||
|
||
id = models.BigAutoField(primary_key=True, db_column='id', verbose_name='自增ID')
|
||
UserUUID = UUIDField(unique=True, db_column='UserUUID', verbose_name='用户UUID')
|
||
SettingsData = models.TextField(default='', db_column='SettingsData', verbose_name='设置数据')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'UserSettings'
|
||
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=_UtcNow, 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):
|
||
"""获取拥有该角色的用户列表"""
|
||
UserModel = _GetUserModel()
|
||
user_uuids = UserRole.objects.filter(
|
||
RoleUUID=self.RoleUUID
|
||
).values_list('UserUUID', flat=True)
|
||
return UserModel.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=_UtcNow, 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 标识该用户-角色关联所属的公司,
|
||
用于菜单按公司差异化显示、数据权限按公司过滤等场景。
|
||
同一用户在不同公司下可绑定不同角色。
|
||
RoleName 为可选的角色名覆盖:非空时前端展示用此名,为空时回退到 Role.RoleName。
|
||
这避免了为每个用户单独建角色,又能让同角色在不同用户视角下显示差异化名称。
|
||
|
||
注意:部门/工作组归属与成员类型(RoleType)不在此表——一律以关联表
|
||
UserRoleDepartment / UserRoleWorkgroup 为准(负责人=关联表 RoleType=1)。
|
||
原 DeptUUID/WorkgroupUUID/RoleType 遗留字段已删除(见 0002 迁移)。
|
||
"""
|
||
|
||
UserRoleUUID = UUIDField(primary_key=True, db_column='UserRoleUUID', verbose_name='关联UUID')
|
||
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
|
||
RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
RoleName = models.CharField(max_length=128, null=True, blank=True, db_column='RoleName', verbose_name='角色名覆盖')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, 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')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID')
|
||
PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'RolePermission'
|
||
verbose_name = '角色权限关联'
|
||
verbose_name_plural = '角色权限关联'
|
||
|
||
|
||
# === RAM 资源访问管理表 ===
|
||
|
||
class RAMGrant(QModel):
|
||
"""RAM 授权表——资源访问管理(Resource Access Management)授权记录
|
||
|
||
RAM 是统一"主体→资源→操作"授权模型(借鉴 AWS RAM/IAM):
|
||
- 主体 Principal:Role(角色)/ User(用户)
|
||
- 资源 Resource:App(应用)/ Menu(菜单)/ data_scope(数据权限,预留)
|
||
- 操作 Action:view(可见)/ use(使用)/ manage(管理)/ grant(授权)
|
||
- 作用域 Scope:授权只在归属租户/公司内生效(null=全租户/全公司,仅 super 可授)
|
||
|
||
与现有权限体系融合:
|
||
- App 可见性 = 安装状态(AppManager.CheckAccess)∩ RAM(角色→App, view)
|
||
- 菜单可见性 = 公司过滤 ∧ 权限码校验 ∧ 应用安装 ∧ RAM(角色→Menu, view)(可选启用)
|
||
- 数据权限暂不接管(RoleScope 四级已实现),预留 ResourceType=data_scope
|
||
"""
|
||
|
||
GrantUUID = UUIDField(primary_key=True, db_column='GrantUUID', verbose_name='授权UUID')
|
||
PrincipalType = models.CharField(max_length=16, db_column='PrincipalType', verbose_name='主体类型(role/user)')
|
||
PrincipalUUID = UUIDField(db_column='PrincipalUUID', verbose_name='主体UUID(角色/用户)')
|
||
ResourceType = models.CharField(max_length=32, db_column='ResourceType', verbose_name='资源类型(app/menu/data_scope)')
|
||
ResourceUUID = UUIDField(db_column='ResourceUUID', verbose_name='资源UUID')
|
||
Action = models.CharField(max_length=16, db_column='Action', verbose_name='操作(view/use/manage/grant)')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='归属租户(null=跨租户)')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='归属公司(null=租户内全部公司)')
|
||
GrantStatus = models.SmallIntegerField(default=1, db_column='GrantStatus', verbose_name='状态(1生效/0撤销)')
|
||
GrantBy = UUIDField(null=True, blank=True, db_column='GrantBy', verbose_name='授权人UUID')
|
||
GrantTime = models.DateTimeField(default=_UtcNow, verbose_name='授权时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'RAMGrant'
|
||
verbose_name = 'RAM授权'
|
||
verbose_name_plural = 'RAM授权'
|
||
unique_together = [
|
||
('PrincipalType', 'PrincipalUUID', 'ResourceType', 'ResourceUUID',
|
||
'Action', 'TenantUUID', 'CompanyUUID'),
|
||
]
|
||
indexes = [
|
||
models.Index(fields=['PrincipalUUID', 'ResourceType'], name='idx_ram_principal'),
|
||
models.Index(fields=['ResourceType', 'ResourceUUID'], name='idx_ram_resource'),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f'{self.PrincipalType}:{self.PrincipalUUID} {self.Action} {self.ResourceType}:{self.ResourceUUID}'
|
||
|
||
|
||
# === 用户 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')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', 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=_UtcNow, verbose_name='创建时间')
|
||
UpdateTime = models.DateTimeField(default=_UtcNow, 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=_UtcNow, 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=_UtcNow, verbose_name='创建时间')
|
||
UpdateTime = models.DateTimeField(default=_UtcNow, 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, CompanyUUID=None):
|
||
"""从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)
|
||
|
||
Args:
|
||
RoleName: 新角色名称
|
||
TenantUUID: 新角色所属租户
|
||
CompanyUUID: 复制出的 RolePermission 所属公司。
|
||
缺省用系统公司 UUID(与主项目 RolePermission 创建一致),
|
||
RolePermission.CompanyUUID 为非空字段,漏传会导致插入失败。
|
||
"""
|
||
with transaction.atomic():
|
||
role = Role.objects.create(
|
||
RoleUUID=_uuid.uuid4().bytes,
|
||
TenantUUID=TenantUUID,
|
||
RoleName=RoleName,
|
||
)
|
||
if CompanyUUID is None:
|
||
CompanyUUID = _uuid.UUID('00000000-0000-0000-0000-000000000001')
|
||
template_perms = PositionTemplatePermission.objects.filter(
|
||
PositionTemplateUUID=self.PositionTemplateUUID
|
||
)
|
||
RolePermission.objects.bulk_create([
|
||
RolePermission(
|
||
RolePermUUID=_uuid.uuid4().bytes,
|
||
RoleUUID=role.RoleUUID,
|
||
CompanyUUID=CompanyUUID,
|
||
PermUUID=tp.PermUUID,
|
||
)
|
||
for tp in template_perms
|
||
])
|
||
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=_UtcNow, 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')
|
||
CategoryUUID = UUIDField(null=True, blank=True, db_column='CategoryUUID', 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=_UtcNow, verbose_name='创建时间')
|
||
UpdateTime = models.DateTimeField(default=_UtcNow, verbose_name='更新时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Position'
|
||
verbose_name = '人事职务标签'
|
||
verbose_name_plural = '人事职务标签'
|
||
|
||
def __str__(self):
|
||
return self.PositionName
|
||
|
||
|
||
# === 通用部门表(职务分类目录) ===
|
||
|
||
class PositionCategory(QModel):
|
||
"""通用部门表——职务分类目录(映射 SAAS 主库 PositionCategory 表)
|
||
|
||
租户级内置分类(如 财务=FIN / 行政=ADM / 运维=OPS / 人事=HR / 通用=GEN),
|
||
每个通用部门有编码缩写。职务绑定通用部门后,编码自动按"缩写+序号"生成
|
||
(如 FIN01、FIN02),同一租户同一通用部门内递增。
|
||
"""
|
||
|
||
PositionCategoryUUID = UUIDField(primary_key=True, db_column='PositionCategoryUUID', verbose_name='通用部门UUID')
|
||
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
|
||
CategoryName = models.CharField(max_length=128, db_column='CategoryName', verbose_name='通用部门名称')
|
||
CategoryCode = models.CharField(max_length=64, db_column='CategoryCode', verbose_name='编码缩写')
|
||
CategorySort = models.SmallIntegerField(default=0, db_column='CategorySort', verbose_name='排序')
|
||
CategoryStatus = models.SmallIntegerField(default=1, db_column='CategoryStatus', verbose_name='状态')
|
||
CategoryDesc = models.CharField(max_length=256, default='', db_column='CategoryDesc', verbose_name='描述')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, verbose_name='创建时间')
|
||
UpdateTime = models.DateTimeField(default=_UtcNow, verbose_name='更新时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'PositionCategory'
|
||
verbose_name = '通用部门'
|
||
verbose_name_plural = '通用部门'
|
||
|
||
def __str__(self):
|
||
return self.CategoryName
|
||
|
||
|
||
# (UserDept 表已废弃——员工部门归属已迁移到 UserRoleDepartment 关联表,不再用独立表)
|
||
|
||
|
||
class AppPosition(QModel):
|
||
"""应用与职务绑定——只有绑定职务的用户才可见/使用该应用(映射 SAAS 主库 AppPosition 表)
|
||
|
||
机制:无该应用绑定记录 = 全员可见(兼容存量);有绑定 = 用户职务集合须与绑定职务有交集。
|
||
"""
|
||
|
||
AppPositionUUID = UUIDField(primary_key=True, db_column='AppPositionUUID', verbose_name='绑定UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
PositionUUID = UUIDField(db_column='PositionUUID', verbose_name='职务UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'AppPosition'
|
||
verbose_name = '应用职务绑定'
|
||
verbose_name_plural = '应用职务绑定'
|
||
|
||
def __str__(self):
|
||
return f'{self.AppUUID} → {self.PositionUUID}'
|
||
|
||
|
||
class AppVirtualPosition(QModel):
|
||
"""应用虚拟职务——应用清单声明的职务抽象(如"查看者/审批者")(映射 SAAS 主库 AppVirtualPosition 表)
|
||
|
||
应用在清单(AppManifest.VirtualPositions)中声明虚拟职务及其可见菜单(MenuNames),
|
||
安装后由管理员将系统实际职务绑定到虚拟职务(AppVirtualBinding),
|
||
用户经实际职务获得虚拟职务声明的菜单可见性。
|
||
"""
|
||
|
||
AppVirtualPositionUUID = UUIDField(primary_key=True, db_column='AppVirtualPositionUUID', verbose_name='虚拟职务UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
Code = models.CharField(max_length=64, db_column='Code', verbose_name='虚拟职务编码(应用内唯一)')
|
||
Name = models.CharField(max_length=128, db_column='Name', verbose_name='虚拟职务名称')
|
||
MenuNames = models.TextField(default='', db_column='MenuNames', verbose_name='可见菜单(Manifest菜单名,分号分隔)')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'AppVirtualPosition'
|
||
unique_together = (('AppUUID', 'Code'),)
|
||
verbose_name = '应用虚拟职务'
|
||
verbose_name_plural = '应用虚拟职务'
|
||
|
||
def __str__(self):
|
||
return f'{self.AppUUID} → {self.Name}'
|
||
|
||
|
||
class AppVirtualBinding(QModel):
|
||
"""应用虚拟职务与实际职务绑定(映射 SAAS 主库 AppVirtualBinding 表)
|
||
|
||
拥有被绑定实际职务的用户,经此获得虚拟职务声明的应用菜单可见性。
|
||
"""
|
||
|
||
AppVirtualBindingUUID = UUIDField(primary_key=True, db_column='AppVirtualBindingUUID', verbose_name='绑定UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
VirtualPositionUUID = UUIDField(db_column='VirtualPositionUUID', verbose_name='虚拟职务UUID')
|
||
PositionUUID = UUIDField(db_column='PositionUUID', verbose_name='实际职务UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'AppVirtualBinding'
|
||
unique_together = (('VirtualPositionUUID', 'PositionUUID'),)
|
||
verbose_name = '应用虚拟职务绑定'
|
||
verbose_name_plural = '应用虚拟职务绑定'
|
||
|
||
def __str__(self):
|
||
return f'{self.VirtualPositionUUID} → {self.PositionUUID}'
|
||
|
||
|
||
# === 菜单表 ===
|
||
|
||
class Menu(QModel):
|
||
"""菜单表——系统导航与功能入口(映射 SAAS 主库 Menu 表)"""
|
||
|
||
MenuUUID = UUIDField(primary_key=True, db_column='MenuUUID', verbose_name='菜单UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
ParentMenuUUID = UUIDField(null=True, blank=True, db_column='ParentMenuUUID', verbose_name='上级菜单UUID')
|
||
MenuName = models.CharField(max_length=128, db_column='MenuName', verbose_name='菜单名称')
|
||
MenuTitle = models.CharField(max_length=128, db_column='MenuTitle', verbose_name='菜单标题')
|
||
MenuIcon = models.CharField(max_length=128, default='', db_column='MenuIcon', verbose_name='菜单图标')
|
||
MenuLink = models.CharField(max_length=256, default='', db_column='MenuLink', verbose_name='菜单链接')
|
||
MenuSort = models.SmallIntegerField(default=0, db_column='MenuSort', verbose_name='排序')
|
||
PermissionLevel = models.FloatField(default=1.0, db_column='PermissionLevel', verbose_name='权限级别')
|
||
PermissionAttr = models.CharField(max_length=256, default='', db_column='PermissionAttr', verbose_name='权限属性')
|
||
MenuStatus = models.SmallIntegerField(default=1, db_column='MenuStatus', verbose_name='菜单状态')
|
||
MenuType = models.SmallIntegerField(default=0, db_column='MenuType', verbose_name='菜单类型')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Menu'
|
||
verbose_name = '菜单'
|
||
verbose_name_plural = '菜单'
|
||
|
||
def __str__(self):
|
||
return self.MenuTitle or self.MenuName
|
||
|
||
|
||
# === 工单表 ===
|
||
|
||
class WorkOrder(QModel):
|
||
"""工单表——任务与问题跟踪(映射 SAAS 主库 WorkOrder 表)"""
|
||
|
||
OrderUUID = UUIDField(primary_key=True, db_column='OrderUUID', verbose_name='工单UUID')
|
||
Title = models.CharField(max_length=256, db_column='Title', verbose_name='标题')
|
||
Description = models.CharField(max_length=4096, default='', db_column='Description', verbose_name='描述')
|
||
Category = models.CharField(max_length=64, default='other', db_column='Category', verbose_name='分类')
|
||
Priority = models.SmallIntegerField(default=0, db_column='Priority', verbose_name='优先级')
|
||
Status = models.SmallIntegerField(default=0, db_column='Status', verbose_name='状态')
|
||
CreatorUUID = UUIDField(db_column='CreatorUUID', db_index=True, verbose_name='创建人UUID')
|
||
AssigneeUUID = UUIDField(null=True, blank=True, db_column='AssigneeUUID', verbose_name='指派人UUID')
|
||
Resolution = models.CharField(max_length=4096, default='', db_column='Resolution', verbose_name='解决方案')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
UpdateTime = models.DateTimeField(null=True, blank=True, db_column='UpdateTime', verbose_name='更新时间')
|
||
CloseTime = models.DateTimeField(null=True, blank=True, db_column='CloseTime', verbose_name='关闭时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'WorkOrder'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '工单'
|
||
verbose_name_plural = '工单'
|
||
|
||
def __str__(self):
|
||
return self.Title
|
||
|
||
|
||
# === 操作审计日志表 ===
|
||
|
||
class AuditLog(QModel):
|
||
"""操作审计日志表——记录敏感表写操作快照(映射 SAAS 主库 AuditLog 表)"""
|
||
|
||
AuditLogUUID = UUIDField(primary_key=True, db_column='AuditLogUUID', verbose_name='日志UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
UserUUID = UUIDField(null=True, blank=True, db_column='UserUUID', verbose_name='操作人UUID')
|
||
TableName = models.CharField(max_length=64, db_index=True, db_column='TableName', verbose_name='表名')
|
||
Action = models.CharField(max_length=16, db_column='Action', verbose_name='操作类型')
|
||
TargetUUID = UUIDField(null=True, blank=True, db_column='TargetUUID', verbose_name='目标记录UUID')
|
||
IPAddress = models.CharField(max_length=64, default='', db_column='IPAddress', verbose_name='IP地址')
|
||
OldData = models.JSONField(null=True, blank=True, db_column='OldData', verbose_name='变更前数据')
|
||
NewData = models.JSONField(null=True, blank=True, db_column='NewData', verbose_name='变更后数据')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_index=True, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'AuditLog'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '操作审计日志'
|
||
verbose_name_plural = '操作审计日志'
|
||
|
||
def __str__(self):
|
||
return f'{self.TableName}/{self.Action}'
|
||
|
||
|
||
# === 物品引用表 ===
|
||
|
||
class ItemReference(QModel):
|
||
"""物品引用表——物品模板定义(映射 SAAS 主库 ItemReference 表)"""
|
||
|
||
ItemUUID = UUIDField(primary_key=True, db_column='ItemUUID', verbose_name='物品UUID')
|
||
ItemPublisherUserUUID = UUIDField(db_column='ItemPublisherUserUUID', verbose_name='发布者UUID')
|
||
ItemName = models.CharField(max_length=128, db_column='ItemName', verbose_name='物品名称')
|
||
ItemIcon = models.CharField(max_length=512, db_column='ItemIcon', verbose_name='物品图标')
|
||
ItemIconColor = models.CharField(max_length=6, db_column='ItemIconColor', verbose_name='图标颜色')
|
||
ItemTags = models.JSONField(default=list, db_column='ItemTags', verbose_name='标签')
|
||
ItemDescription = models.TextField(default='', db_column='ItemDescription', verbose_name='描述')
|
||
ItemMetaData = models.JSONField(default=dict, db_column='ItemMetaData', verbose_name='元数据')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'ItemReference'
|
||
verbose_name = '物品模板'
|
||
verbose_name_plural = '物品模板'
|
||
|
||
def __str__(self):
|
||
return self.ItemName
|
||
|
||
|
||
# === 背包表 ===
|
||
|
||
class Backpack(QModel):
|
||
"""背包表——用户物品栏(映射 SAAS 主库 Backpack 表)"""
|
||
|
||
BackpackItemUUID = UUIDField(primary_key=True, db_column='BackpackItemUUID', verbose_name='背包物品UUID')
|
||
UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID')
|
||
ItemTemplateUUID = UUIDField(db_column='ItemTemplateUUID', verbose_name='物品模板UUID')
|
||
_ItemName = models.CharField(max_length=128, null=True, blank=True, db_column='_ItemName', verbose_name='物品名称缓存')
|
||
_ItemIcon = models.CharField(max_length=512, null=True, blank=True, db_column='_ItemIcon', verbose_name='物品图标缓存')
|
||
_ItemIconColor = models.CharField(max_length=6, null=True, blank=True, db_column='_ItemIconColor', verbose_name='图标颜色缓存')
|
||
_ItemTags = models.JSONField(null=True, blank=True, db_column='_ItemTags', verbose_name='标签缓存')
|
||
_ItemMetaData = models.JSONField(null=True, blank=True, db_column='_ItemMetaData', verbose_name='元数据缓存')
|
||
_ItemDescription = models.TextField(null=True, blank=True, db_column='_ItemDescription', verbose_name='描述缓存')
|
||
ItemLength = models.BigIntegerField(default=1, db_column='ItemLength', verbose_name='数量')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Backpack'
|
||
verbose_name = '背包'
|
||
verbose_name_plural = '背包'
|
||
|
||
@property
|
||
def ItemReference(self):
|
||
return self.ItemTemplateUUID
|
||
|
||
@property
|
||
def ItemNameResolved(self):
|
||
return self._ItemName
|
||
|
||
@property
|
||
def ItemIconResolved(self):
|
||
return self._ItemIcon
|
||
|
||
def __str__(self):
|
||
return f'{self._ItemName or self.ItemTemplateUUID}x{self.ItemLength}'
|
||
|
||
|
||
# === 文件资产表 ===
|
||
|
||
class FileAsset(QModel):
|
||
"""文件资产表——文件存储与引用管理(映射 SAAS 主库 FileAsset 表)"""
|
||
|
||
AssetUUID = UUIDField(primary_key=True, db_column='AssetUUID', verbose_name='资产UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
FileName = models.CharField(max_length=256, db_column='FileName', verbose_name='文件名')
|
||
FileSHA1 = models.CharField(max_length=40, unique=True, db_column='FileSHA1', verbose_name='文件SHA1')
|
||
FileSize = models.IntegerField(db_column='FileSize', verbose_name='文件大小')
|
||
MimeType = models.CharField(max_length=128, db_column='MimeType', verbose_name='MIME类型')
|
||
StoragePath = models.CharField(max_length=512, db_column='StoragePath', verbose_name='存储路径')
|
||
UploaderUUID = UUIDField(db_column='UploaderUUID', verbose_name='上传者UUID')
|
||
RefCount = models.IntegerField(default=0, db_column='RefCount', verbose_name='引用计数')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'FileAsset'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '文件资产'
|
||
verbose_name_plural = '文件资产'
|
||
|
||
def __str__(self):
|
||
return self.FileName
|
||
|
||
|
||
class CompanyInfo(QModel):
|
||
"""企业信息表——工商注册与企业详情(映射 SAAS 主库 CompanyInfo 表)"""
|
||
|
||
CompanyInfoUUID = UUIDField(primary_key=True, db_column='CompanyInfoUUID', verbose_name='企业信息UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
EnterpriseName = models.CharField(max_length=256, db_column='EnterpriseName', verbose_name='企业全称')
|
||
EnterpriseShortName = models.CharField(max_length=100, db_column='EnterpriseShortName', verbose_name='企业简称')
|
||
CreditCode = models.CharField(max_length=64, unique=True, db_column='CreditCode', verbose_name='统一社会信用代码')
|
||
EnterpriseType = models.CharField(max_length=32, default='', db_column='EnterpriseType', verbose_name='企业类型')
|
||
OwnershipType = models.CharField(max_length=32, default='', db_column='OwnershipType', verbose_name='所有制类型')
|
||
Industry = models.CharField(max_length=32, default='', db_column='Industry', verbose_name='行业')
|
||
Scale = models.CharField(max_length=32, default='', db_column='Scale', verbose_name='规模')
|
||
RegisteredCapital = models.CharField(max_length=64, default='', db_column='RegisteredCapital', verbose_name='注册资本')
|
||
EstablishDate = models.CharField(max_length=32, default='', db_column='EstablishDate', verbose_name='成立日期')
|
||
BusinessScope = models.TextField(default='', db_column='BusinessScope', verbose_name='经营范围')
|
||
BusinessStatus = models.CharField(max_length=32, default='', db_column='BusinessStatus', verbose_name='经营状态')
|
||
RegProvince = models.CharField(max_length=32, default='', db_column='RegProvince', verbose_name='注册省份')
|
||
RegCity = models.CharField(max_length=32, default='', db_column='RegCity', verbose_name='注册城市')
|
||
RegDistrict = models.CharField(max_length=32, default='', db_column='RegDistrict', verbose_name='注册区县')
|
||
RegAddress = models.CharField(max_length=512, default='', db_column='RegAddress', verbose_name='注册地址')
|
||
OfficeProvince = models.CharField(max_length=32, default='', db_column='OfficeProvince', verbose_name='办公省份')
|
||
OfficeCity = models.CharField(max_length=32, default='', db_column='OfficeCity', verbose_name='办公城市')
|
||
OfficeDistrict = models.CharField(max_length=32, default='', db_column='OfficeDistrict', verbose_name='办公区县')
|
||
OfficeAddress = models.CharField(max_length=512, default='', db_column='OfficeAddress', verbose_name='办公地址')
|
||
Phone = models.CharField(max_length=32, default='', db_column='Phone', verbose_name='电话')
|
||
Phone2 = models.CharField(max_length=32, default='', db_column='Phone2', verbose_name='电话2')
|
||
Fax = models.CharField(max_length=32, default='', db_column='Fax', verbose_name='传真')
|
||
Email = models.CharField(max_length=128, default='', db_column='Email', verbose_name='邮箱')
|
||
Hotline = models.CharField(max_length=32, default='', db_column='Hotline', verbose_name='热线')
|
||
ServicePhone = models.CharField(max_length=32, default='', db_column='ServicePhone', verbose_name='客服电话')
|
||
Website = models.CharField(max_length=256, default='', db_column='Website', verbose_name='网站')
|
||
Wechat = models.CharField(max_length=128, default='', db_column='Wechat', verbose_name='微信')
|
||
WechatMini = models.CharField(max_length=128, default='', db_column='WechatMini', verbose_name='微信小程序')
|
||
Weibo = models.CharField(max_length=128, default='', db_column='Weibo', verbose_name='微博')
|
||
LinkedIn = models.CharField(max_length=256, default='', db_column='LinkedIn', verbose_name='领英')
|
||
Bank = models.CharField(max_length=32, default='', db_column='Bank', verbose_name='开户银行')
|
||
BankAccount = models.CharField(max_length=64, default='', db_column='BankAccount', verbose_name='银行账号')
|
||
BankBranch = models.CharField(max_length=128, default='', db_column='BankBranch', verbose_name='开户支行')
|
||
BankProvince = models.CharField(max_length=32, default='', db_column='BankProvince', verbose_name='银行省份')
|
||
BankCity = models.CharField(max_length=32, default='', db_column='BankCity', verbose_name='银行城市')
|
||
AccountName = models.CharField(max_length=128, default='', db_column='AccountName', verbose_name='账户名')
|
||
BankCode = models.CharField(max_length=32, default='', db_column='BankCode', verbose_name='银行代码')
|
||
AccountType = models.CharField(max_length=32, default='', db_column='AccountType', verbose_name='账户类型')
|
||
AccountStatus = models.CharField(max_length=32, default='', db_column='AccountStatus', verbose_name='账户状态')
|
||
TaxBank = models.CharField(max_length=128, default='', db_column='TaxBank', verbose_name='税务银行')
|
||
TaxAccount = models.CharField(max_length=64, default='', db_column='TaxAccount', verbose_name='税务账号')
|
||
TaxBankCode = models.CharField(max_length=32, default='', db_column='TaxBankCode', verbose_name='税务银行代码')
|
||
SalaryBank = models.CharField(max_length=128, default='', db_column='SalaryBank', verbose_name='薪资银行')
|
||
SalaryAccount = models.CharField(max_length=64, default='', db_column='SalaryAccount', verbose_name='薪资账号')
|
||
SocialSecurityBank = models.CharField(max_length=128, default='', db_column='SocialSecurityBank', verbose_name='社保银行')
|
||
SocialSecurityAccount = models.CharField(max_length=64, default='', db_column='SocialSecurityAccount', verbose_name='社保账号')
|
||
LegalPersonName = models.CharField(max_length=64, default='', db_column='LegalPersonName', verbose_name='法人姓名')
|
||
LegalPersonIDCard = models.CharField(max_length=32, default='', db_column='LegalPersonIdCard', verbose_name='法人身份证')
|
||
LegalPersonGender = models.CharField(max_length=16, default='', db_column='LegalPersonGender', verbose_name='法人性别')
|
||
LegalPersonBirthday = models.CharField(max_length=32, default='', db_column='LegalPersonBirthday', verbose_name='法人生日')
|
||
LegalPersonNationality = models.CharField(max_length=32, default='', db_column='LegalPersonNationality', verbose_name='法人国籍')
|
||
LegalPersonEducation = models.CharField(max_length=32, default='', db_column='LegalPersonEducation', verbose_name='法人学历')
|
||
LegalPersonPhone = models.CharField(max_length=32, default='', db_column='LegalPersonPhone', verbose_name='法人电话')
|
||
LegalPersonEmail = models.CharField(max_length=128, default='', db_column='LegalPersonEmail', verbose_name='法人邮箱')
|
||
LegalPersonAddress = models.CharField(max_length=256, default='', db_column='LegalPersonAddress', verbose_name='法人地址')
|
||
LegalPersonPosition = models.CharField(max_length=32, default='', db_column='LegalPersonPosition', verbose_name='法人职务')
|
||
LegalPersonShareRatio = models.CharField(max_length=16, default='', db_column='LegalPersonShareRatio', verbose_name='法人持股比例')
|
||
LegalPersonAppointment = models.CharField(max_length=32, default='', db_column='LegalPersonAppointment', verbose_name='法人任职')
|
||
OperatorName = models.CharField(max_length=64, default='', db_column='OperatorName', verbose_name='经办人姓名')
|
||
OperatorIDCard = models.CharField(max_length=32, default='', db_column='OperatorIdCard', verbose_name='经办人身份证')
|
||
OperatorPhone = models.CharField(max_length=32, default='', db_column='OperatorPhone', verbose_name='经办人电话')
|
||
OperatorEmail = models.CharField(max_length=128, default='', db_column='OperatorEmail', verbose_name='经办人邮箱')
|
||
OperatorDepartment = models.CharField(max_length=128, default='', db_column='OperatorDepartment', verbose_name='经办人部门')
|
||
OperatorPosition = models.CharField(max_length=64, default='', db_column='OperatorPosition', verbose_name='经办人职务')
|
||
FinanceManagerName = models.CharField(max_length=64, default='', db_column='FinanceManagerName', verbose_name='财务负责人')
|
||
FinanceManagerPhone = models.CharField(max_length=32, default='', db_column='FinanceManagerPhone', verbose_name='财务电话')
|
||
FinanceManagerEmail = models.CharField(max_length=128, default='', db_column='FinanceManagerEmail', verbose_name='财务邮箱')
|
||
FinanceManagerIDCard = models.CharField(max_length=32, default='', db_column='FinanceManagerIdCard', verbose_name='财务身份证')
|
||
Remark = models.TextField(default='', db_column='Remark', verbose_name='备注')
|
||
ReviewStatus = models.SmallIntegerField(default=0, db_column='ReviewStatus', verbose_name='审核状态')
|
||
ReviewRemark = models.CharField(max_length=512, default='', db_column='ReviewRemark', verbose_name='审核备注')
|
||
ReviewerUUID = UUIDField(null=True, blank=True, db_column='ReviewerUUID', verbose_name='审核人UUID')
|
||
ReviewTime = models.DateTimeField(null=True, blank=True, db_column='ReviewTime', verbose_name='审核时间')
|
||
CreatorUUID = UUIDField(null=True, blank=True, db_column='CreatorUUID', verbose_name='创建人UUID')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'CompanyInfo'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '企业信息'
|
||
verbose_name_plural = '企业信息'
|
||
|
||
|
||
class DepartmentSMTM(QModel):
|
||
"""部门主管表——部门负责人映射(映射 SAAS 主库 DepartmentSMTM 表)"""
|
||
|
||
SMTMUUID = UUIDField(primary_key=True, db_column='SMTMUUID', verbose_name='关联UUID')
|
||
DepartmentUUID = UUIDField(db_column='DepartmentUUID', verbose_name='部门UUID')
|
||
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
|
||
PersonInChargeStatus = models.SmallIntegerField(default=0, db_column='PersonInChargeStatus', verbose_name='负责人状态')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'DepartmentSMTM'
|
||
verbose_name = '部门主管'
|
||
verbose_name_plural = '部门主管'
|
||
|
||
|
||
class Workgroup(QModel):
|
||
"""工作组表——跨部门协作单元(映射 SAAS 主库 Workgroup 表)"""
|
||
|
||
WorkgroupUUID = UUIDField(primary_key=True, db_column='WorkgroupUUID', verbose_name='工作组UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
WorkgroupName = models.CharField(max_length=256, db_column='WorkgroupName', verbose_name='工作组名称')
|
||
WorkgroupEnglishName = models.CharField(max_length=256, db_column='WorkgroupEnglishName', verbose_name='工作组英文名')
|
||
WorkgroupIntroduction = models.CharField(max_length=1024, db_column='WorkgroupIntroduction', verbose_name='工作组介绍')
|
||
WorkgroupType = models.SmallIntegerField(default=0, db_column='WorkgroupType', verbose_name='工作组类型') # 0=部门工作组,1=跨部门工作组
|
||
WorkgroupTags = models.CharField(max_length=512, null=True, blank=True, db_column='WorkgroupTags', verbose_name='工作组标签')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Workgroup'
|
||
verbose_name = '工作组'
|
||
verbose_name_plural = '工作组'
|
||
|
||
|
||
class SimulatedTrigger(QModel):
|
||
"""模拟触发器表——自动化规则引擎(映射 SAAS 主库 SimulatedTrigger 表)"""
|
||
|
||
SimulatedTriggerUUID = UUIDField(primary_key=True, db_column='SimulatedTriggerUUID', verbose_name='触发器UUID')
|
||
SimulatedTriggerPublisherUserUUID = UUIDField(db_column='SimulatedTriggerPublisherUserUUID', verbose_name='发布者UUID')
|
||
SimulatedTriggerName = models.CharField(max_length=128, db_column='SimulatedTriggerName', verbose_name='触发器名称')
|
||
SimulatedTriggerFilePath = models.CharField(max_length=512, db_column='SimulatedTriggerFilePath', verbose_name='文件路径')
|
||
SimulatedTriggerCode = models.TextField(db_column='SimulatedTriggerCode', verbose_name='触发器代码')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'SimulatedTrigger'
|
||
verbose_name = '模拟触发器'
|
||
verbose_name_plural = '模拟触发器'
|
||
|
||
|
||
class CashReceiptsPaymentsLedger(QModel):
|
||
"""现金收支台账表——财务流水记录(映射 SAAS 主库 CashBook 表)"""
|
||
|
||
CashBookUUID = UUIDField(primary_key=True, db_column='CashBookUUID', verbose_name='台账UUID')
|
||
CashBookCreateTime = models.DateTimeField(default=_UtcNow, db_column='CashBookCreateTime', verbose_name='创建时间')
|
||
CashBookTransactionType = models.SmallIntegerField(default=0, db_column='CashBookTransactionType', verbose_name='交易类型')
|
||
CashBookAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, db_column='CashBookAmount', verbose_name='金额')
|
||
CashBookCurrency = models.CharField(max_length=10, default='CNY', db_column='CashBookCurrency', verbose_name='币种')
|
||
CashBookDescription = models.CharField(max_length=512, db_column='CashBookDescription', verbose_name='描述')
|
||
CashBookTransactionTime = models.DateTimeField(default=_UtcNow, db_column='CashBookTransactionTime', verbose_name='交易时间')
|
||
CashBookUserUUID = UUIDField(db_column='CashBookUserUUID', verbose_name='用户UUID')
|
||
CashBookStatus = models.SmallIntegerField(default=0, db_column='CashBookStatus', verbose_name='状态')
|
||
CashBookRemark = models.CharField(max_length=1024, null=True, blank=True, db_column='CashBookRemark', verbose_name='备注')
|
||
CashBookMetaData = models.JSONField(null=True, blank=True, db_column='CashBookMetaData', verbose_name='元数据')
|
||
CashBookCategory = models.CharField(max_length=50, default='其它', db_column='CashBookCategory', verbose_name='分类')
|
||
CashBookPaymentMethod = models.CharField(max_length=50, default='', db_column='CashBookPaymentMethod', verbose_name='支付方式')
|
||
CashBookPaymentDetails = models.CharField(max_length=512, null=True, blank=True, db_column='CashBookPaymentDetails', verbose_name='支付详情')
|
||
CashBookInvoiceNo = models.CharField(max_length=100, null=True, blank=True, db_column='CashBookInvoiceNo', verbose_name='发票号')
|
||
CashBookTaxRate = models.DecimalField(max_digits=4, decimal_places=2, default=0.00, db_column='CashBookTaxRate', verbose_name='税率')
|
||
CashBookTaxAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, db_column='CashBookTaxAmount', verbose_name='税额')
|
||
CashBookNetAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, db_column='CashBookNetAmount', verbose_name='净额')
|
||
CashBookProjectUUID = UUIDField(null=True, blank=True, db_column='CashBookProjectUUID', verbose_name='项目UUID')
|
||
CashBookOrderUUID = UUIDField(null=True, blank=True, db_column='CashBookOrderUUID', verbose_name='订单UUID')
|
||
CashBookDepartmentUUID = UUIDField(null=True, blank=True, db_column='CashBookDepartmentUUID', verbose_name='部门UUID')
|
||
CashBookAttachmentPath = models.CharField(max_length=512, null=True, blank=True, db_column='CashBookAttachmentPath', verbose_name='附件路径')
|
||
CashBookExchangeRate = models.DecimalField(max_digits=10, decimal_places=4, default=1.0000, db_column='CashBookExchangeRate', verbose_name='汇率')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'CashBook'
|
||
verbose_name = '现金收支台账'
|
||
verbose_name_plural = '现金收支台账'
|
||
# 索引由业务侧(gvsds)迁移声明;SDK 为 managed=False 镜像,不重复声明(避免 models.E030)
|
||
|
||
|
||
class Asset(QModel):
|
||
"""资产表——企业资产记录(映射 SAAS 主库 Asset 表)"""
|
||
|
||
AssetUUID = UUIDField(primary_key=True, db_column='AssetUUID', verbose_name='资产UUID')
|
||
AssetType = models.CharField(max_length=50, db_column='AssetType', verbose_name='资产类型')
|
||
AssetAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, db_column='AssetAmount', verbose_name='金额')
|
||
AssetCurrency = models.CharField(max_length=10, default='CNY', db_column='AssetCurrency', verbose_name='币种')
|
||
AssetDescription = models.CharField(max_length=512, db_column='AssetDescription', verbose_name='描述')
|
||
AssetCreateTime = models.DateTimeField(default=_UtcNow, db_column='AssetCreateTime', verbose_name='创建时间')
|
||
AssetUserUUID = UUIDField(db_column='AssetUserUUID', verbose_name='用户UUID')
|
||
AssetStatus = models.SmallIntegerField(default=0, db_column='AssetStatus', verbose_name='状态')
|
||
AssetMetaData = models.JSONField(null=True, blank=True, db_column='AssetMetaData', verbose_name='元数据')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Asset'
|
||
verbose_name = '资产'
|
||
verbose_name_plural = '资产'
|
||
|
||
|
||
class Liability(QModel):
|
||
"""负债表——企业负债记录(映射 SAAS 主库 Liability 表)"""
|
||
|
||
LiabilityUUID = UUIDField(primary_key=True, db_column='LiabilityUUID', verbose_name='负债UUID')
|
||
LiabilityType = models.CharField(max_length=50, db_column='LiabilityType', verbose_name='负债类型')
|
||
LiabilityAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, db_column='LiabilityAmount', verbose_name='金额')
|
||
LiabilityCurrency = models.CharField(max_length=10, default='CNY', db_column='LiabilityCurrency', verbose_name='币种')
|
||
LiabilityDescription = models.CharField(max_length=512, db_column='LiabilityDescription', verbose_name='描述')
|
||
LiabilityCreateTime = models.DateTimeField(default=_UtcNow, db_column='LiabilityCreateTime', verbose_name='创建时间')
|
||
LiabilityUserUUID = UUIDField(db_column='LiabilityUserUUID', verbose_name='用户UUID')
|
||
LiabilityStatus = models.SmallIntegerField(default=0, db_column='LiabilityStatus', verbose_name='状态')
|
||
LiabilityMetaData = models.JSONField(null=True, blank=True, db_column='LiabilityMetaData', verbose_name='元数据')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'Liability'
|
||
verbose_name = '负债'
|
||
verbose_name_plural = '负债'
|
||
|
||
|
||
# === 应用体系(应用市场 / 安装授权 / 应用配置) ===
|
||
|
||
class App(QModel):
|
||
"""应用/功能注册表——企业功能与个人应用的统一实体(映射 SAAS 主库 App 表)
|
||
|
||
投放形态 AppType:enterprise(企业功能)/ personal(个人应用)/ both(混合);
|
||
AuthRequired=1 时个人使用须经租户管理员审批(UserApp.ApplyStatus 状态机)。
|
||
"""
|
||
|
||
AppUUID = UUIDField(primary_key=True, db_column='AppUUID', verbose_name='应用UUID')
|
||
AppCode = models.CharField(max_length=64, unique=True, db_column='AppCode', verbose_name='应用编码')
|
||
AppName = models.CharField(max_length=128, db_column='AppName', verbose_name='应用名称')
|
||
AppIcon = models.CharField(max_length=256, default='', db_column='AppIcon', verbose_name='应用图标')
|
||
AppDesc = models.TextField(default='', db_column='AppDesc', verbose_name='应用描述')
|
||
AppType = models.CharField(max_length=16, default='both', db_column='AppType', verbose_name='投放形态')
|
||
AuthRequired = models.SmallIntegerField(default=0, db_column='AuthRequired', verbose_name='是否需企业授权(0/1)')
|
||
MenuRootUUID = UUIDField(null=True, blank=True, db_column='MenuRootUUID', verbose_name='关联菜单根UUID')
|
||
DefaultPerms = models.TextField(default='', db_column='DefaultPerms', verbose_name='默认权限码集(;分隔)')
|
||
FrontendRoute = models.CharField(max_length=256, default='', db_column='FrontendRoute', verbose_name='前端路由')
|
||
FrontendType = models.CharField(max_length=16, default='vue', db_column='FrontendType', verbose_name='前端类型(vue/legacy)')
|
||
LegacySrc = models.CharField(max_length=256, default='', db_column='LegacySrc', verbose_name='旧版页面路径')
|
||
AppSort = models.IntegerField(default=0, db_column='AppSort', verbose_name='排序')
|
||
AppStatus = models.SmallIntegerField(default=1, db_column='AppStatus', verbose_name='状态(1启用/0禁用)')
|
||
StrictPositionVisible = models.SmallIntegerField(default=0, db_column='StrictPositionVisible', verbose_name='严格职务可见(0宽松/1严格)')
|
||
RequirePositionBinding = models.SmallIntegerField(default=0, db_column='RequirePositionBinding', verbose_name='强制职务绑定(0/1)')
|
||
Source = models.CharField(max_length=16, default='local', db_column='Source', verbose_name='来源(local/msync)')
|
||
ServiceName = models.CharField(max_length=128, default='', blank=True, db_column='ServiceName', verbose_name='提供方服务名')
|
||
ServiceDomain = models.CharField(max_length=256, default='', blank=True, db_column='ServiceDomain', verbose_name='提供方服务域名')
|
||
ServiceOnline = models.SmallIntegerField(default=1, db_column='ServiceOnline', verbose_name='提供方服务在线(1在线/0离线)')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'App'
|
||
ordering = ['AppSort', 'CreateTime']
|
||
verbose_name = '应用/功能'
|
||
verbose_name_plural = '应用/功能'
|
||
|
||
def __str__(self):
|
||
return self.AppName
|
||
|
||
|
||
class TenantApp(QModel):
|
||
"""企业安装/授权(租户维度)——租户与应用的关系(映射 SAAS 主库 TenantApp 表)
|
||
|
||
InstallStatus:1=已安装(启用) / 0=已停用
|
||
AuthStatus :0=未授权 / 1=已授权 / 2=已拒绝
|
||
"""
|
||
|
||
TenantAppUUID = UUIDField(primary_key=True, db_column='TenantAppUUID', verbose_name='企业安装记录UUID')
|
||
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
InstallStatus = models.SmallIntegerField(default=1, db_column='InstallStatus', verbose_name='安装状态(1启用/0停用)')
|
||
AuthStatus = models.SmallIntegerField(default=0, db_column='AuthStatus', verbose_name='企业授权状态(0/1/2)')
|
||
InstallTime = models.DateTimeField(default=_UtcNow, db_column='InstallTime', verbose_name='安装时间')
|
||
AuthBy = UUIDField(null=True, blank=True, db_column='AuthBy', verbose_name='企业授权人UUID')
|
||
AuthTime = models.DateTimeField(null=True, blank=True, db_column='AuthTime', verbose_name='企业授权时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'TenantApp'
|
||
unique_together = (('TenantUUID', 'AppUUID'),)
|
||
verbose_name = '企业应用安装'
|
||
verbose_name_plural = '企业应用安装'
|
||
|
||
|
||
class CompanyApp(QModel):
|
||
"""企业应用安装(公司维度)——指定公司安装(映射 SAAS 主库 CompanyApp 表)
|
||
|
||
与 TenantApp 互补:TenantApp=全租户安装,CompanyApp=指定公司安装;
|
||
企业功能可用判定:租户已装 或 当前公司已装。
|
||
"""
|
||
|
||
CompanyAppUUID = UUIDField(primary_key=True, db_column='CompanyAppUUID', verbose_name='公司安装记录UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
InstallStatus = models.SmallIntegerField(default=1, db_column='InstallStatus', verbose_name='安装状态(1启用/0停用)')
|
||
InstallTime = models.DateTimeField(default=_UtcNow, db_column='InstallTime', verbose_name='安装时间')
|
||
InstallBy = UUIDField(null=True, blank=True, db_column='InstallBy', verbose_name='安装人UUID')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'CompanyApp'
|
||
unique_together = (('CompanyUUID', 'AppUUID'),)
|
||
verbose_name = '企业应用安装(公司维度)'
|
||
verbose_name_plural = '企业应用安装(公司维度)'
|
||
|
||
|
||
class UserApp(QModel):
|
||
"""个人应用启用/申请——用户与应用的关系(映射 SAAS 主库 UserApp 表)
|
||
|
||
UseStatus :1=已启用 / 0=已停用
|
||
ApplyStatus:0=未申请 / 1=申请中 / 2=已授权 / 3=已拒绝
|
||
"""
|
||
|
||
UserAppUUID = UUIDField(primary_key=True, db_column='UserAppUUID', verbose_name='个人应用记录UUID')
|
||
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
UseStatus = models.SmallIntegerField(default=1, db_column='UseStatus', verbose_name='使用状态(1启用/0停用)')
|
||
ApplyStatus = models.SmallIntegerField(default=0, db_column='ApplyStatus', verbose_name='授权申请状态(0/1/2/3)')
|
||
ApplyTime = models.DateTimeField(null=True, blank=True, db_column='ApplyTime', verbose_name='申请时间')
|
||
ApproveBy = UUIDField(null=True, blank=True, db_column='ApproveBy', verbose_name='审批人UUID')
|
||
ApproveTime = models.DateTimeField(null=True, blank=True, db_column='ApproveTime', verbose_name='审批时间')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'UserApp'
|
||
unique_together = (('UserUUID', 'AppUUID'),)
|
||
verbose_name = '个人应用'
|
||
verbose_name_plural = '个人应用'
|
||
|
||
|
||
class AppConfig(QModel):
|
||
"""应用配置键值表——按租户+公司隔离的应用运行参数(映射 SAAS 主库 AppConfig 表)
|
||
|
||
CompanyUUID 为空 → 租户级默认配置;有值 → 公司级配置(覆盖租户默认)。
|
||
"""
|
||
|
||
AppConfigUUID = UUIDField(primary_key=True, db_column='AppConfigUUID', verbose_name='应用配置UUID')
|
||
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID(空=租户级默认)')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
ConfigKey = models.CharField(max_length=128, db_column='ConfigKey', verbose_name='配置键名')
|
||
ConfigValue = models.TextField(default='', db_column='ConfigValue', verbose_name='配置值')
|
||
UpdateBy = UUIDField(null=True, blank=True, db_column='UpdateBy', verbose_name='更新人UUID')
|
||
UpdateTime = models.DateTimeField(null=True, blank=True, db_column='UpdateTime', verbose_name='更新时间')
|
||
CreateTime = models.DateTimeField(default=_UtcNow, db_column='CreateTime', verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
managed = False
|
||
app_label = 'gvsdsdk'
|
||
db_table = 'AppConfig'
|
||
verbose_name = '应用配置'
|
||
verbose_name_plural = '应用配置'
|