Files
TermiNexus b5474834cd fix(models): 修正 UserIDCard 镜像表名并补齐两列
UserIDCard 原 db_table 为 UserIDCard,实际表对应 gerp.UserIDCard.db_table='IDCard'(lower_case_table_names=1 下解析为 idcard),导致 SDK 查询直接报 1146;同时补齐镜像缺失的 Appointments / ReadmeReport。校验差异 51 → 13(余下均为本地库缺表)。
2026-09-13 22:19:17 +08:00

1769 lines
94 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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 = '应用配置'