- 新增 gvsds/apps/gerp/apps/<appid>/:talk/mailbox/gitea/oa/ehrm/dingcalc_finance/ product_order/shop/store_value/wallet/enterprise,各应用自带 models/manager/views/urls 与 apps.gerp_manifests(JSON 清单,$schema 指向 apps/gerp_manifests.schema.json) - 删除 app_manifests.py 内联 _LEGACY_MANIFESTS,改为扫描各应用清单文件(未知键/缺必填键即判非法并跳过) - 路由按应用拆:/API/Mail/、/API/Gitea/、/API/Oa/ 由扁平直挂改为 include 子应用 urls.py (reverse() 断言 19 个 name 的路径逐字不变) - AppConfig 移至 gerp_app.py 并改显式点分路径注册,规避 apps/ 目录与 Django <app>.apps 同名冲突 - gerp/models.py 裁剪已搬迁模型(保留 _NewUuidBytes 与平台模型),修复 views/__init__.py 与 oidc.py 导入 - AssetDownloadView:优先按 FileAsset.StoragePath 定位并回退 ./resource 旧布局,修上传成功却下载 404 - 同批落地 Talk 应用后端(app 已迁入 apps/talk,gerp.0046 状态认领迁移) - 注意:app.ini 被 .gitignore 忽略,INSTALLED_APPS 需在部署环境手工同步 gvsds.apps.gerp.gerp_app.GerpConfig
913 lines
50 KiB
Python
913 lines
50 KiB
Python
"""模块:GERP 数据模型
|
||
定义企业资源管理核心表:租户、公司、部门、角色、权限、岗位模板、
|
||
人事职务标签、工作流、消息、菜单、资产、负债、
|
||
现金簿、背包、物品、个人主页等。
|
||
|
||
权限体系四大分层:
|
||
1. 组织人事层:Tenant → Company → Department,员工通过 UserRoleDepartment/UserRoleWorkgroup 关联表归属部门/工作组
|
||
2. 岗位模板层:PositionTemplate + PositionTemplatePermission,权限初始化快照
|
||
3. 角色权限载体层:Role + RolePermission,运行时鉴权实体
|
||
4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性)
|
||
|
||
所有 ORM 字段与方法遵循帕斯卡命名法(首字母大写、缩写大写、单单词小写)。"""
|
||
|
||
from gvsdsdk.model_base import QModel
|
||
import datetime
|
||
import uuid
|
||
from django.db import models
|
||
from gvsds.apps.core.models import UUIDField, User
|
||
|
||
|
||
def _NewUuidBytes():
|
||
"""生成随机 16 字节 UUID(主键默认值,迁移可序列化)。"""
|
||
return uuid.uuid4().bytes
|
||
|
||
|
||
class Tenant(QModel):
|
||
"""租户表——多租户架构核心"""
|
||
|
||
TenantUUID = UUIDField(primary_key=True, verbose_name='租户UUID')
|
||
TenantName = models.CharField(max_length=256, verbose_name='租户名称')
|
||
TenantStatus = models.SmallIntegerField(default=1, verbose_name='租户状态')
|
||
TenantExpireTime = models.DateTimeField(verbose_name='到期时间')
|
||
ReviewStatus = models.SmallIntegerField(default=0, verbose_name='审核状态')
|
||
ReviewRemark = models.CharField(max_length=512, default='', verbose_name='审核备注')
|
||
ReviewerUUID = UUIDField(null=True, blank=True, verbose_name='审核人UUID')
|
||
ReviewTime = models.DateTimeField(null=True, blank=True, verbose_name='审核时间')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'Tenant'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '租户'
|
||
verbose_name_plural = '租户'
|
||
|
||
def __str__(self):
|
||
return self.TenantName
|
||
|
||
|
||
class CompanyInfo(QModel):
|
||
"""企业信息表——工商注册与企业详情"""
|
||
|
||
CompanyInfoUUID = UUIDField(primary_key=True, verbose_name='企业信息UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, db_column='TenantUUID', verbose_name='租户UUID')
|
||
EnterpriseName = models.CharField(max_length=256, verbose_name='企业全称')
|
||
EnterpriseShortName = models.CharField(max_length=100, verbose_name='企业简称')
|
||
CreditCode = models.CharField(max_length=64, unique=True, verbose_name='统一社会信用代码')
|
||
EnterpriseType = models.CharField(max_length=32, default='', verbose_name='企业类型')
|
||
OwnershipType = models.CharField(max_length=32, default='', verbose_name='所有制类型')
|
||
Industry = models.CharField(max_length=32, default='', verbose_name='行业')
|
||
Scale = models.CharField(max_length=32, default='', verbose_name='规模')
|
||
RegisteredCapital = models.CharField(max_length=64, default='', verbose_name='注册资本')
|
||
EstablishDate = models.CharField(max_length=32, default='', verbose_name='成立日期')
|
||
BusinessScope = models.TextField(default='', verbose_name='经营范围')
|
||
BusinessStatus = models.CharField(max_length=32, default='', verbose_name='经营状态')
|
||
RegProvince = models.CharField(max_length=32, default='', verbose_name='注册省份')
|
||
RegCity = models.CharField(max_length=32, default='', verbose_name='注册城市')
|
||
RegDistrict = models.CharField(max_length=32, default='', verbose_name='注册区县')
|
||
RegAddress = models.CharField(max_length=512, default='', verbose_name='注册地址')
|
||
OfficeProvince = models.CharField(max_length=32, default='', verbose_name='办公省份')
|
||
OfficeCity = models.CharField(max_length=32, default='', verbose_name='办公城市')
|
||
OfficeDistrict = models.CharField(max_length=32, default='', verbose_name='办公区县')
|
||
OfficeAddress = models.CharField(max_length=512, default='', verbose_name='办公地址')
|
||
Phone = models.CharField(max_length=32, default='', verbose_name='电话')
|
||
Phone2 = models.CharField(max_length=32, default='', verbose_name='电话2')
|
||
Fax = models.CharField(max_length=32, default='', verbose_name='传真')
|
||
Email = models.CharField(max_length=128, default='', verbose_name='邮箱')
|
||
Hotline = models.CharField(max_length=32, default='', verbose_name='热线')
|
||
ServicePhone = models.CharField(max_length=32, default='', verbose_name='客服电话')
|
||
Website = models.CharField(max_length=256, default='', verbose_name='网站')
|
||
Wechat = models.CharField(max_length=128, default='', verbose_name='微信')
|
||
WechatMini = models.CharField(max_length=128, default='', verbose_name='微信小程序')
|
||
Weibo = models.CharField(max_length=128, default='', verbose_name='微博')
|
||
LinkedIn = models.CharField(max_length=256, default='', verbose_name='领英')
|
||
Bank = models.CharField(max_length=32, default='', verbose_name='开户银行')
|
||
BankAccount = models.CharField(max_length=64, default='', verbose_name='银行账号')
|
||
BankBranch = models.CharField(max_length=128, default='', verbose_name='开户支行')
|
||
BankProvince = models.CharField(max_length=32, default='', verbose_name='银行省份')
|
||
BankCity = models.CharField(max_length=32, default='', verbose_name='银行城市')
|
||
AccountName = models.CharField(max_length=128, default='', verbose_name='账户名')
|
||
BankCode = models.CharField(max_length=32, default='', verbose_name='银行代码')
|
||
AccountType = models.CharField(max_length=32, default='', verbose_name='账户类型')
|
||
AccountStatus = models.CharField(max_length=32, default='', verbose_name='账户状态')
|
||
TaxBank = models.CharField(max_length=128, default='', verbose_name='税务银行')
|
||
TaxAccount = models.CharField(max_length=64, default='', verbose_name='税务账号')
|
||
TaxBankCode = models.CharField(max_length=32, default='', verbose_name='税务银行代码')
|
||
SalaryBank = models.CharField(max_length=128, default='', verbose_name='薪资银行')
|
||
SalaryAccount = models.CharField(max_length=64, default='', verbose_name='薪资账号')
|
||
SocialSecurityBank = models.CharField(max_length=128, default='', verbose_name='社保银行')
|
||
SocialSecurityAccount = models.CharField(max_length=64, default='', verbose_name='社保账号')
|
||
LegalPersonName = models.CharField(max_length=64, default='', verbose_name='法人姓名')
|
||
LegalPersonIDCard = models.CharField(max_length=32, default='', db_column='LegalPersonIdCard', verbose_name='法人身份证')
|
||
LegalPersonGender = models.CharField(max_length=16, default='', verbose_name='法人性别')
|
||
LegalPersonBirthday = models.CharField(max_length=32, default='', verbose_name='法人生日')
|
||
LegalPersonNationality = models.CharField(max_length=32, default='', verbose_name='法人国籍')
|
||
LegalPersonEducation = models.CharField(max_length=32, default='', verbose_name='法人学历')
|
||
LegalPersonPhone = models.CharField(max_length=32, default='', verbose_name='法人电话')
|
||
LegalPersonEmail = models.CharField(max_length=128, default='', verbose_name='法人邮箱')
|
||
LegalPersonAddress = models.CharField(max_length=256, default='', verbose_name='法人地址')
|
||
LegalPersonPosition = models.CharField(max_length=32, default='', verbose_name='法人职务')
|
||
LegalPersonShareRatio = models.CharField(max_length=16, default='', verbose_name='法人持股比例')
|
||
LegalPersonAppointment = models.CharField(max_length=32, default='', verbose_name='法人任职')
|
||
OperatorName = models.CharField(max_length=64, default='', verbose_name='经办人姓名')
|
||
OperatorIDCard = models.CharField(max_length=32, default='', db_column='OperatorIdCard', verbose_name='经办人身份证')
|
||
OperatorPhone = models.CharField(max_length=32, default='', verbose_name='经办人电话')
|
||
OperatorEmail = models.CharField(max_length=128, default='', verbose_name='经办人邮箱')
|
||
OperatorDepartment = models.CharField(max_length=128, default='', verbose_name='经办人部门')
|
||
OperatorPosition = models.CharField(max_length=64, default='', verbose_name='经办人职务')
|
||
FinanceManagerName = models.CharField(max_length=64, default='', verbose_name='财务负责人')
|
||
FinanceManagerPhone = models.CharField(max_length=32, default='', verbose_name='财务电话')
|
||
FinanceManagerEmail = models.CharField(max_length=128, default='', verbose_name='财务邮箱')
|
||
FinanceManagerIDCard = models.CharField(max_length=32, default='', db_column='FinanceManagerIdCard', verbose_name='财务身份证')
|
||
Remark = models.TextField(default='', verbose_name='备注')
|
||
ReviewStatus = models.SmallIntegerField(default=0, verbose_name='审核状态')
|
||
ReviewRemark = models.CharField(max_length=512, default='', verbose_name='审核备注')
|
||
ReviewerUUID = UUIDField(null=True, blank=True, verbose_name='审核人UUID')
|
||
ReviewTime = models.DateTimeField(null=True, blank=True, verbose_name='审核时间')
|
||
CreatorUUID = UUIDField(null=True, blank=True, verbose_name='创建人UUID')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'CompanyInfo'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '企业信息'
|
||
verbose_name_plural = '企业信息'
|
||
|
||
|
||
class Company(QModel):
|
||
"""公司表——系统内组织机构"""
|
||
|
||
CompanyUUID = UUIDField(primary_key=True, 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, verbose_name='公司全称')
|
||
CompanyShort = models.CharField(max_length=100, verbose_name='公司简称')
|
||
CompanyIcon = models.CharField(max_length=512, null=True, blank=True, db_column='CompanyIcon', verbose_name='公司图标')
|
||
CompanyStatus = models.SmallIntegerField(default=1, verbose_name='公司状态')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'Company'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '公司'
|
||
verbose_name_plural = '公司'
|
||
|
||
def __str__(self):
|
||
return self.CompanyName
|
||
|
||
|
||
class Department(QModel):
|
||
"""部门表——组织架构层级"""
|
||
|
||
DeptUUID = UUIDField(primary_key=True, verbose_name='部门UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, 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, verbose_name='部门名称')
|
||
DeptSort = models.IntegerField(default=0, verbose_name='排序')
|
||
DeptStatus = models.SmallIntegerField(default=1, verbose_name='部门状态')
|
||
PositionCategoryUUID = UUIDField(null=True, blank=True, db_column='PositionCategoryUUID', verbose_name='绑定通用部门UUID')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'Dept'
|
||
app_label = 'gerp'
|
||
ordering = ['DeptSort', '-CreateTime']
|
||
verbose_name = '部门'
|
||
verbose_name_plural = '部门'
|
||
|
||
def __str__(self):
|
||
return self.DeptName
|
||
|
||
|
||
class FileAsset(QModel):
|
||
"""文件资产表——文件存储与引用管理"""
|
||
|
||
AssetUUID = UUIDField(primary_key=True, verbose_name='资产UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
FileName = models.CharField(max_length=256, verbose_name='文件名')
|
||
FileSHA1 = models.CharField(max_length=40, unique=True, verbose_name='文件SHA1')
|
||
FileSize = models.IntegerField(verbose_name='文件大小')
|
||
MimeType = models.CharField(max_length=128, verbose_name='MIME类型')
|
||
StoragePath = models.CharField(max_length=512, verbose_name='存储路径')
|
||
UploaderUUID = UUIDField(verbose_name='上传者UUID')
|
||
RefCount = models.IntegerField(default=0, verbose_name='引用计数')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'FileAsset'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '文件资产'
|
||
verbose_name_plural = '文件资产'
|
||
|
||
|
||
class WorkOrder(QModel):
|
||
"""工单表——任务与问题跟踪"""
|
||
|
||
OrderUUID = UUIDField(primary_key=True, verbose_name='工单UUID')
|
||
Title = models.CharField(max_length=256, verbose_name='标题')
|
||
Description = models.CharField(max_length=4096, default='', verbose_name='描述')
|
||
Category = models.CharField(max_length=64, default='other', verbose_name='分类')
|
||
Priority = models.SmallIntegerField(default=0, verbose_name='优先级')
|
||
Status = models.SmallIntegerField(default=0, verbose_name='状态')
|
||
CreatorUUID = UUIDField(db_index=True, db_column='CreatorUUID', verbose_name='创建人UUID')
|
||
AssigneeUUID = UUIDField(null=True, blank=True, db_column='AssigneeUUID', verbose_name='指派人UUID')
|
||
Resolution = models.CharField(max_length=4096, default='', verbose_name='解决方案')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
UpdateTime = models.DateTimeField(null=True, blank=True, verbose_name='更新时间')
|
||
CloseTime = models.DateTimeField(null=True, blank=True, verbose_name='关闭时间')
|
||
|
||
class Meta:
|
||
db_table = 'WorkOrder'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '工单'
|
||
verbose_name_plural = '工单'
|
||
|
||
|
||
class Message(QModel):
|
||
"""消息表——系统通知与站内信"""
|
||
|
||
MessageUUID = UUIDField(primary_key=True, 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, verbose_name='标题')
|
||
Content = models.TextField(default='', verbose_name='内容')
|
||
MsgType = models.SmallIntegerField(default=0, verbose_name='消息类型')
|
||
IsRead = models.SmallIntegerField(default=0, verbose_name='已读')
|
||
RelatedUUID = models.CharField(max_length=64, default='', verbose_name='关联UUID')
|
||
RelatedType = models.CharField(max_length=32, default='', verbose_name='关联类型')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'Message'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '消息'
|
||
verbose_name_plural = '消息'
|
||
|
||
|
||
class Menu(QModel):
|
||
"""菜单表——系统导航与功能入口"""
|
||
|
||
MenuUUID = UUIDField(primary_key=True, verbose_name='菜单UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, 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, verbose_name='菜单名称')
|
||
MenuTitle = models.CharField(max_length=128, verbose_name='菜单标题')
|
||
MenuIcon = models.CharField(max_length=128, default='', verbose_name='菜单图标')
|
||
MenuLink = models.CharField(max_length=256, default='', verbose_name='菜单链接')
|
||
MenuSort = models.SmallIntegerField(default=0, verbose_name='排序')
|
||
PermissionLevel = models.FloatField(default=1.0, verbose_name='权限级别')
|
||
PermissionAttr = models.CharField(max_length=256, default='', verbose_name='权限属性')
|
||
MenuStatus = models.SmallIntegerField(default=1, verbose_name='菜单状态')
|
||
MenuType = models.SmallIntegerField(default=0, verbose_name='菜单类型')
|
||
|
||
class Meta:
|
||
db_table = 'Menu'
|
||
app_label = 'gerp'
|
||
verbose_name = '菜单'
|
||
verbose_name_plural = '菜单'
|
||
|
||
|
||
class LegacyDepartment(QModel):
|
||
"""遗留部门表——历史数据兼容"""
|
||
|
||
DepartmentUUID = UUIDField(primary_key=True, verbose_name='部门UUID')
|
||
DepartmentName = models.CharField(max_length=256, unique=True, verbose_name='部门名称')
|
||
DepartmentEnglishName = models.CharField(max_length=256, unique=True, verbose_name='部门英文名')
|
||
DepartmentIntroduction = models.CharField(max_length=1024, verbose_name='部门介绍')
|
||
|
||
class Meta:
|
||
db_table = 'Department'
|
||
app_label = 'gerp'
|
||
verbose_name = '遗留部门'
|
||
verbose_name_plural = '遗留部门'
|
||
|
||
|
||
class DepartmentSMTM(QModel):
|
||
"""部门主管表——部门负责人映射"""
|
||
|
||
SMTMUUID = UUIDField(primary_key=True, verbose_name='关联UUID')
|
||
DepartmentUUID = UUIDField(verbose_name='部门UUID')
|
||
UserUUID = UUIDField(verbose_name='用户UUID')
|
||
PersonInChargeStatus = models.SmallIntegerField(default=0, verbose_name='负责人状态')
|
||
|
||
class Meta:
|
||
db_table = 'DepartmentSMTM'
|
||
app_label = 'gerp'
|
||
verbose_name = '部门主管'
|
||
verbose_name_plural = '部门主管'
|
||
|
||
|
||
class Workgroup(QModel):
|
||
"""工作组表——跨部门协作单元,归属特定公司"""
|
||
|
||
WorkgroupUUID = UUIDField(primary_key=True, verbose_name='工作组UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
WorkgroupName = models.CharField(max_length=256, verbose_name='工作组名称')
|
||
WorkgroupEnglishName = models.CharField(max_length=256, verbose_name='工作组英文名')
|
||
WorkgroupIntroduction = models.CharField(max_length=1024, verbose_name='工作组介绍')
|
||
WorkgroupType = models.SmallIntegerField(default=0, verbose_name='工作组类型') # 0=部门工作组,1=跨部门工作组
|
||
WorkgroupTags = models.CharField(max_length=512, null=True, blank=True, verbose_name='工作组标签') # 逗号分隔,如 团队,项目组
|
||
|
||
class Meta:
|
||
db_table = 'Workgroup'
|
||
app_label = 'gerp'
|
||
verbose_name = '工作组'
|
||
verbose_name_plural = '工作组'
|
||
|
||
|
||
class WorkgroupRole(QModel):
|
||
"""工作组角色关联表——工作组与角色的多对多映射
|
||
|
||
工作组只绑定角色,不直接绑定用户。用户通过持有该角色即成为工作组成员。
|
||
RoleType 标识该角色在工作组中的类型:0=组员,1=组长。
|
||
CompanyUUID 冗余存储用于 SAAS 数据隔离过滤(与 UserRole 表设计一致)。
|
||
"""
|
||
|
||
WorkgroupRoleUUID = UUIDField(primary_key=True, verbose_name='关联UUID')
|
||
WorkgroupUUID = UUIDField(db_column='WorkgroupUUID', verbose_name='工作组UUID')
|
||
RoleUUID = UUIDField(verbose_name='角色UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
RoleType = models.SmallIntegerField(default=0, verbose_name='角色类型')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'WorkgroupRole'
|
||
app_label = 'gerp'
|
||
verbose_name = '工作组角色关联'
|
||
verbose_name_plural = '工作组角色关联'
|
||
|
||
|
||
class UserRoleWorkgroup(QModel):
|
||
"""用户-角色对 与 工作组 的多对多关联表
|
||
|
||
解决"一个 (用户↔角色) 对可同时加入多个工作组"的需求:
|
||
UserRole 表自身只有单值 WorkgroupUUID 字段(一对一只挂一个工作组),
|
||
本表通过 UserRoleUUID + WorkgroupUUID 建立多对多映射,一个用户-角色对可挂多个工作组。
|
||
|
||
CompanyUUID 冗余存储用于 SAAS 数据隔离过滤(与 UserRole 表设计一致)。
|
||
"""
|
||
|
||
UserRoleWorkgroupUUID = UUIDField(primary_key=True, verbose_name='关联UUID')
|
||
UserRoleUUID = UUIDField(verbose_name='用户-角色关联UUID')
|
||
WorkgroupUUID = UUIDField(db_column='WorkgroupUUID', verbose_name='工作组UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
# 职务不再存于本表:工作组指向公司的员工关系,职务复用 UserRoleDepartmentPosition 多值子表
|
||
RoleType = models.SmallIntegerField(default=0, verbose_name='角色类型')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'UserRoleWorkgroup'
|
||
app_label = 'gerp'
|
||
verbose_name = '用户角色工作组关联'
|
||
verbose_name_plural = '用户角色工作组关联'
|
||
|
||
|
||
class UserRoleDepartment(QModel):
|
||
"""用户-角色对 与 部门 的多对多关联表
|
||
|
||
解决"一个 (用户↔角色) 对可同时加入多个部门"的需求:
|
||
UserRole 表自身只有单值 DeptUUID 字段(且数据库主键是联合主键 (UserUUID, RoleUUID),
|
||
同一用户-角色对只能存在一行,挂新部门会撞 1062 Duplicate entry),
|
||
本表通过 UserRoleUUID + DeptUUID 建立多对多映射,一个用户-角色对可挂多个部门。
|
||
|
||
CompanyUUID 冗余存储用于 SAAS 数据隔离过滤(与 UserRole 表设计一致)。
|
||
"""
|
||
|
||
UserRoleDepartmentUUID = UUIDField(primary_key=True, verbose_name='关联UUID')
|
||
UserRoleUUID = UUIDField(verbose_name='用户-角色关联UUID')
|
||
DeptUUID = UUIDField(db_column='DeptUUID', verbose_name='部门UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
# PositionUUID 已多值化:职务改存 UserRoleDepartmentPosition 子表(身兼数职)
|
||
RoleType = models.SmallIntegerField(default=0, verbose_name='角色类型')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'UserRoleDepartment'
|
||
app_label = 'gerp'
|
||
verbose_name = '用户角色部门关联'
|
||
verbose_name_plural = '用户角色部门关联'
|
||
|
||
|
||
# (UserRoleDepartmentPosition 已下沉 gvsdsdk.models —— 职务多值子表统一由 SDK 提供)
|
||
|
||
|
||
class ItemReference(QModel):
|
||
"""物品引用表——物品模板定义"""
|
||
|
||
ItemUUID = UUIDField(primary_key=True, verbose_name='物品UUID')
|
||
ItemPublisherUserUUID = UUIDField(verbose_name='发布者UUID')
|
||
ItemName = models.CharField(max_length=128, verbose_name='物品名称')
|
||
ItemIcon = models.CharField(max_length=512, verbose_name='物品图标')
|
||
ItemIconColor = models.CharField(max_length=6, verbose_name='图标颜色')
|
||
ItemTags = models.JSONField(verbose_name='标签')
|
||
ItemDescription = models.TextField(verbose_name='描述')
|
||
ItemMetaData = models.JSONField(verbose_name='元数据')
|
||
|
||
class Meta:
|
||
db_table = 'ItemReference'
|
||
app_label = 'gerp'
|
||
verbose_name = '物品模板'
|
||
verbose_name_plural = '物品模板'
|
||
|
||
|
||
class Backpack(QModel):
|
||
"""背包表——用户物品栏"""
|
||
|
||
BackpackItemUUID = UUIDField(primary_key=True, verbose_name='背包物品UUID')
|
||
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
|
||
ItemTemplateUUID = UUIDField(db_column='ItemTemplateUUID', verbose_name='物品模板UUID')
|
||
_ItemName = models.CharField(max_length=128, null=True, blank=True, verbose_name='物品名称缓存')
|
||
_ItemIcon = models.CharField(max_length=512, null=True, blank=True, verbose_name='物品图标缓存')
|
||
_ItemIconColor = models.CharField(max_length=6, null=True, blank=True, verbose_name='图标颜色缓存')
|
||
_ItemTags = models.JSONField(null=True, blank=True, verbose_name='标签缓存')
|
||
_ItemMetaData = models.JSONField(null=True, blank=True, verbose_name='元数据缓存')
|
||
_ItemDescription = models.TextField(null=True, blank=True, verbose_name='描述缓存')
|
||
ItemLength = models.BigIntegerField(verbose_name='数量')
|
||
|
||
class Meta:
|
||
db_table = 'Backpack'
|
||
app_label = 'gerp'
|
||
verbose_name = '背包'
|
||
verbose_name_plural = '背包'
|
||
|
||
@property
|
||
def ItemReference(self):
|
||
"""获取物品模板引用"""
|
||
return self.ItemTemplateUUID
|
||
|
||
@property
|
||
def ItemNameResolved(self):
|
||
"""解析物品名称——优先使用缓存值"""
|
||
ref = self.ItemTemplateUUID
|
||
return self._ItemName or (ref.ItemName if ref else None)
|
||
|
||
@property
|
||
def ItemIconResolved(self):
|
||
"""解析物品图标——优先使用缓存值"""
|
||
ref = self.ItemTemplateUUID
|
||
return self._ItemIcon or (ref.ItemIcon if ref else None)
|
||
|
||
|
||
class SimulatedTrigger(QModel):
|
||
"""模拟触发器表——自动化规则引擎"""
|
||
|
||
SimulatedTriggerUUID = UUIDField(primary_key=True, verbose_name='触发器UUID')
|
||
SimulatedTriggerPublisherUserUUID = UUIDField(verbose_name='发布者UUID')
|
||
SimulatedTriggerName = models.CharField(max_length=128, verbose_name='触发器名称')
|
||
SimulatedTriggerFilePath = models.CharField(max_length=512, verbose_name='文件路径')
|
||
SimulatedTriggerCode = models.TextField(verbose_name='触发器代码')
|
||
|
||
class Meta:
|
||
db_table = 'SimulatedTrigger'
|
||
app_label = 'gerp'
|
||
verbose_name = '模拟触发器'
|
||
verbose_name_plural = '模拟触发器'
|
||
|
||
|
||
class CashReceiptsPaymentsLedger(QModel):
|
||
"""现金收支台账表——财务流水记录"""
|
||
|
||
CashBookUUID = UUIDField(primary_key=True, verbose_name='台账UUID')
|
||
CashBookCreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
CashBookTransactionType = models.SmallIntegerField(default=0, verbose_name='交易类型')
|
||
CashBookAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='金额')
|
||
CashBookCurrency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
|
||
CashBookDescription = models.CharField(max_length=512, verbose_name='描述')
|
||
CashBookTransactionTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='交易时间')
|
||
CashBookUserUUID = UUIDField(verbose_name='用户UUID')
|
||
CashBookStatus = models.SmallIntegerField(default=0, verbose_name='状态')
|
||
CashBookRemark = models.CharField(max_length=1024, null=True, blank=True, verbose_name='备注')
|
||
CashBookMetaData = models.JSONField(null=True, blank=True, verbose_name='元数据')
|
||
CashBookCategory = models.CharField(max_length=50, default='\u5176\u5b83', verbose_name='分类')
|
||
CashBookPaymentMethod = models.CharField(max_length=50, default='', verbose_name='支付方式')
|
||
CashBookPaymentDetails = models.CharField(max_length=512, null=True, blank=True, verbose_name='支付详情')
|
||
CashBookInvoiceNo = models.CharField(max_length=100, null=True, blank=True, verbose_name='发票号')
|
||
CashBookTaxRate = models.DecimalField(max_digits=4, decimal_places=2, default=0.00, verbose_name='税率')
|
||
CashBookTaxAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='税额')
|
||
CashBookNetAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='净额')
|
||
CashBookProjectUUID = UUIDField(null=True, blank=True, verbose_name='项目UUID')
|
||
CashBookOrderUUID = UUIDField(null=True, blank=True, verbose_name='订单UUID')
|
||
CashBookDepartmentUUID = UUIDField(null=True, blank=True, verbose_name='部门UUID')
|
||
CashBookAttachmentPath = models.CharField(max_length=512, null=True, blank=True, verbose_name='附件路径')
|
||
CashBookExchangeRate = models.DecimalField(max_digits=10, decimal_places=4, default=1.0000, verbose_name='汇率')
|
||
|
||
class Meta:
|
||
db_table = 'CashBook'
|
||
app_label = 'gerp'
|
||
verbose_name = '现金收支台账'
|
||
verbose_name_plural = '现金收支台账'
|
||
indexes = [
|
||
models.Index(fields=['CashBookTransactionTime'], name='idx_transaction_time'),
|
||
models.Index(fields=['CashBookTransactionType'], name='idx_transaction_type'),
|
||
models.Index(fields=['CashBookStatus'], name='idx_status'),
|
||
models.Index(fields=['CashBookCategory'], name='idx_category'),
|
||
]
|
||
|
||
|
||
class Asset(QModel):
|
||
"""资产表——企业资产记录"""
|
||
|
||
AssetUUID = UUIDField(primary_key=True, verbose_name='资产UUID')
|
||
AssetType = models.CharField(max_length=50, verbose_name='资产类型')
|
||
AssetAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='金额')
|
||
AssetCurrency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
|
||
AssetDescription = models.CharField(max_length=512, verbose_name='描述')
|
||
AssetCreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
AssetUserUUID = UUIDField(verbose_name='用户UUID')
|
||
AssetStatus = models.SmallIntegerField(default=0, verbose_name='状态')
|
||
AssetMetaData = models.JSONField(null=True, blank=True, verbose_name='元数据')
|
||
|
||
class Meta:
|
||
db_table = 'Asset'
|
||
app_label = 'gerp'
|
||
verbose_name = '资产'
|
||
verbose_name_plural = '资产'
|
||
|
||
|
||
class Liability(QModel):
|
||
"""负债表——企业负债记录"""
|
||
|
||
LiabilityUUID = UUIDField(primary_key=True, verbose_name='负债UUID')
|
||
LiabilityType = models.CharField(max_length=50, verbose_name='负债类型')
|
||
LiabilityAmount = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='金额')
|
||
LiabilityCurrency = models.CharField(max_length=10, default='CNY', verbose_name='币种')
|
||
LiabilityDescription = models.CharField(max_length=512, verbose_name='描述')
|
||
LiabilityCreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
LiabilityUserUUID = UUIDField(verbose_name='用户UUID')
|
||
LiabilityStatus = models.SmallIntegerField(default=0, verbose_name='状态')
|
||
LiabilityMetaData = models.JSONField(null=True, blank=True, verbose_name='元数据')
|
||
|
||
class Meta:
|
||
db_table = 'Liability'
|
||
app_label = 'gerp'
|
||
verbose_name = '负债'
|
||
verbose_name_plural = '负债'
|
||
|
||
|
||
class UserIDCard(QModel):
|
||
"""用户身份证表——实名信息"""
|
||
|
||
UserUUID = UUIDField(primary_key=True, verbose_name='用户UUID')
|
||
Name = models.CharField(max_length=128, verbose_name='姓名')
|
||
OldName = models.CharField(max_length=128, default='', verbose_name='曾用名')
|
||
Born = models.DateField(verbose_name='出生日期')
|
||
Sex = models.CharField(max_length=10, verbose_name='性别')
|
||
Ethnic = models.CharField(max_length=20, verbose_name='民族')
|
||
IdentificationNumber = models.CharField(max_length=20, verbose_name='身份证号')
|
||
StudentRegistrationNumber = models.CharField(max_length=30, default='', verbose_name='学籍号')
|
||
ChangeStudentStatus = models.JSONField(default=list, verbose_name='学籍变动')
|
||
CorrespondenceAddress = models.JSONField(default=list, verbose_name='通讯地址')
|
||
HeadImage = models.CharField(max_length=4096, default='', verbose_name='头像')
|
||
InformationAboutFamilyMembers = models.JSONField(default=dict, verbose_name='家庭成员信息')
|
||
Appointments = models.JSONField(default=dict, verbose_name='任职信息')
|
||
ReadmeReport = models.TextField(default='', verbose_name='自述报告')
|
||
|
||
class Meta:
|
||
db_table = 'IDCard'
|
||
app_label = 'gerp'
|
||
verbose_name = '身份证'
|
||
verbose_name_plural = '身份证'
|
||
|
||
|
||
class PersonalPage(QModel):
|
||
"""个人主页表——用户展示页面"""
|
||
|
||
PersonalPageUserUUID = UUIDField(primary_key=True, verbose_name='用户UUID')
|
||
PersonalPageName = models.CharField(max_length=256, unique=True, verbose_name='主页名称')
|
||
Subtitle = models.CharField(max_length=256, default='', verbose_name='副标题')
|
||
BackgroundLink = models.CharField(max_length=256, default='', verbose_name='背景链接')
|
||
Classification = models.CharField(max_length=256, default='', verbose_name='分类')
|
||
Github = models.CharField(max_length=256, default='', verbose_name='GitHub')
|
||
Wechat = models.CharField(max_length=256, default='', verbose_name='微信')
|
||
QQ = models.CharField(max_length=256, default='', db_column='Qq', verbose_name='QQ')
|
||
SelfIntroduction = models.TextField(default='', verbose_name='自我介绍')
|
||
ProjectExperience = models.CharField(max_length=256, default='', verbose_name='项目经验')
|
||
SatisfiedCustomers = models.CharField(max_length=256, default='', verbose_name='满意客户')
|
||
ReceiveAward = models.CharField(max_length=256, default='', verbose_name='获奖')
|
||
CoffeeCups = models.CharField(max_length=256, default='', verbose_name='咖啡杯数')
|
||
Achievement1Icon = models.CharField(max_length=256, default='', verbose_name='成就1图标')
|
||
Achievement1Name = models.CharField(max_length=256, default='', verbose_name='成就1名称')
|
||
Achievement1Time = models.CharField(max_length=256, default='', verbose_name='成就1时间')
|
||
Achievement1Introduction = models.TextField(default='', verbose_name='成就1介绍')
|
||
Achievement2Icon = models.CharField(max_length=256, default='', verbose_name='成就2图标')
|
||
Achievement2Name = models.CharField(max_length=256, default='', verbose_name='成就2名称')
|
||
Achievement2Time = models.CharField(max_length=256, default='', verbose_name='成就2时间')
|
||
Achievement2Introduction = models.TextField(default='', verbose_name='成就2介绍')
|
||
Achievement3Icon = models.CharField(max_length=256, default='', verbose_name='成就3图标')
|
||
Achievement3Name = models.CharField(max_length=256, default='', verbose_name='成就3名称')
|
||
Achievement3Time = models.CharField(max_length=256, default='', verbose_name='成就3时间')
|
||
Achievement3Introduction = models.TextField(default='', verbose_name='成就3介绍')
|
||
Achievement4Icon = models.CharField(max_length=256, default='', verbose_name='成就4图标')
|
||
Achievement4Name = models.CharField(max_length=256, default='', verbose_name='成就4名称')
|
||
Achievement4Time = models.CharField(max_length=256, default='', verbose_name='成就4时间')
|
||
Achievement4Introduction = models.TextField(default='', verbose_name='成就4介绍')
|
||
Tags = models.CharField(max_length=256, default='', verbose_name='标签')
|
||
Markdown = models.TextField(default='', verbose_name='Markdown')
|
||
|
||
class Meta:
|
||
db_table = 'PersonalPage'
|
||
app_label = 'gerp'
|
||
verbose_name = '个人主页'
|
||
verbose_name_plural = '个人主页'
|
||
|
||
|
||
class UserSettings(QModel):
|
||
"""用户设置表——存储每位用户的个性化系统设置"""
|
||
|
||
UserUUID = UUIDField(unique=True, db_column='UserUUID', verbose_name='用户UUID')
|
||
SettingsData = models.TextField(default='', verbose_name='设置数据')
|
||
|
||
class Meta:
|
||
db_table = 'UserSettings'
|
||
app_label = 'gerp'
|
||
verbose_name = '用户设置'
|
||
verbose_name_plural = '用户设置'
|
||
|
||
|
||
class AuditLog(QModel):
|
||
"""操作审计日志表——记录敏感表的写操作(增/删/改)数据快照
|
||
|
||
仅记录 User/Role/Permission/Tenant/Company 等敏感表的写操作,
|
||
按公司隔离查询(CompanyUUID 字段),含变更前后的数据快照。
|
||
通过管理命令 python manage.py CleanAuditLogs --days=90 自动清理过期日志。
|
||
"""
|
||
|
||
AuditLogUUID = UUIDField(primary_key=True, 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, verbose_name='表名')
|
||
Action = models.CharField(max_length=16, verbose_name='操作类型')
|
||
TargetUUID = UUIDField(null=True, blank=True, verbose_name='目标记录UUID')
|
||
IPAddress = models.CharField(max_length=64, default='', verbose_name='IP地址')
|
||
OldData = models.JSONField(null=True, blank=True, verbose_name='变更前数据')
|
||
NewData = models.JSONField(null=True, blank=True, verbose_name='变更后数据')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, db_index=True, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'AuditLog'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '操作审计日志'
|
||
verbose_name_plural = '操作审计日志'
|
||
|
||
def __str__(self):
|
||
return f'{self.TableName}:{self.Action} @ {self.CreateTime}'
|
||
|
||
|
||
class AbnormalUserLog(QModel):
|
||
"""异常用户攻击日志表——记录恶意越权/攻击请求的完整指纹
|
||
|
||
由 AttackDetectMiddleware 自动记录:403/429 响应、重复鉴权失败、
|
||
越权访问尝试等。按公司隔离查询,用于安全审计与攻击溯源。
|
||
"""
|
||
|
||
LogUUID = UUIDField(primary_key=True, 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')
|
||
AccountID = models.CharField(max_length=64, default='', db_index=True, verbose_name='账号标识')
|
||
IPAddress = models.CharField(max_length=64, default='', db_index=True, verbose_name='客户端IP')
|
||
RealIP = models.CharField(max_length=64, default='', verbose_name='真实IP')
|
||
XForwardedFor = models.CharField(max_length=512, default='', verbose_name='XFF头')
|
||
UserAgent = models.CharField(max_length=512, default='', verbose_name='User-Agent')
|
||
RequestMethod = models.CharField(max_length=16, default='', verbose_name='请求方法')
|
||
RequestPath = models.CharField(max_length=512, default='', db_index=True, verbose_name='请求路径')
|
||
RequestParams = models.JSONField(null=True, blank=True, verbose_name='查询参数')
|
||
RequestBody = models.TextField(default='', verbose_name='请求体摘要')
|
||
RequestHeaders = models.JSONField(null=True, blank=True, verbose_name='请求头')
|
||
AttemptType = models.CharField(max_length=32, default='', db_index=True, verbose_name='攻击类型')
|
||
ResponseStatus = models.IntegerField(default=0, verbose_name='响应状态码')
|
||
Remark = models.CharField(max_length=512, default='', verbose_name='备注')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, db_index=True, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'AbnormalUserLog'
|
||
app_label = 'gerp'
|
||
ordering = ['-CreateTime']
|
||
verbose_name = '异常用户攻击日志'
|
||
verbose_name_plural = '异常用户攻击日志'
|
||
|
||
def __str__(self):
|
||
return f'{self.AttemptType}:{self.IPAddress} @ {self.CreateTime}'
|
||
|
||
|
||
class App(QModel):
|
||
"""应用/功能注册表——企业功能与个人应用的统一实体
|
||
|
||
企业视角叫"功能"(租户安装后全员可用),个人视角叫"应用"(个人启用后自用)。
|
||
投放形态 AppType:
|
||
enterprise —— 企业功能:仅租户管理员安装后该租户可见
|
||
personal —— 个人应用:个人启用后仅本人可见
|
||
both —— 混合:企业装=全员,个人装=自用
|
||
企业授权 AuthRequired=1:个人使用须经租户管理员审批授权(UserApp.ApplyStatus 状态机)。
|
||
"""
|
||
|
||
AppUUID = UUIDField(primary_key=True, verbose_name='应用UUID')
|
||
AppCode = models.CharField(max_length=64, unique=True, verbose_name='应用编码')
|
||
AppName = models.CharField(max_length=128, verbose_name='应用名称')
|
||
AppIcon = models.CharField(max_length=256, default='', verbose_name='应用图标')
|
||
AppDesc = models.TextField(default='', verbose_name='应用描述')
|
||
AppType = models.CharField(max_length=16, default='both', verbose_name='投放形态(enterprise/personal/both)')
|
||
AuthRequired = models.SmallIntegerField(default=0, verbose_name='是否需企业授权(0/1)')
|
||
MenuRootUUID = UUIDField(null=True, blank=True, verbose_name='关联菜单根UUID(安装后注入)')
|
||
DefaultPerms = models.TextField(default='', verbose_name='默认权限码集(;分隔)')
|
||
FrontendRoute = models.CharField(max_length=256, default='', verbose_name='前端路由(安装后跳转)')
|
||
FrontendType = models.CharField(max_length=16, default='vue', verbose_name='前端类型(vue/legacy)')
|
||
LegacySrc = models.CharField(max_length=256, default='', verbose_name='旧版页面路径(legacy)')
|
||
AppSort = models.IntegerField(default=0, verbose_name='排序')
|
||
AppStatus = models.SmallIntegerField(default=1, verbose_name='状态(1启用/0禁用)')
|
||
StrictPositionVisible = models.SmallIntegerField(default=0, verbose_name='严格职务可见(0宽松/1严格)')
|
||
RequirePositionBinding = models.SmallIntegerField(default=0, verbose_name='强制职务绑定(0/1,应用清单声明默认要求职务)')
|
||
# 远端注入(MSYNC):Source=msync 的应用由子服务器上报注册
|
||
Source = models.CharField(max_length=16, default='local', verbose_name='来源(local/msync)')
|
||
ServiceName = models.CharField(max_length=128, default='', blank=True, verbose_name='提供方服务名')
|
||
ServiceDomain = models.CharField(max_length=256, default='', blank=True, verbose_name='提供方服务域名')
|
||
ServiceOnline = models.SmallIntegerField(default=1, verbose_name='提供方服务在线(1在线/0离线)')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'App'
|
||
app_label = 'gerp'
|
||
ordering = ['AppSort', 'CreateTime']
|
||
verbose_name = '应用/功能'
|
||
verbose_name_plural = '应用/功能'
|
||
|
||
def __str__(self):
|
||
return self.AppName
|
||
|
||
|
||
class TenantApp(QModel):
|
||
"""企业安装/授权——租户与应用的关系
|
||
|
||
InstallStatus: 1=已安装(启用) / 0=已停用
|
||
AuthStatus: 0=未授权 / 1=已授权 / 2=已拒绝(AuthRequired 应用的企业级开通状态)
|
||
"""
|
||
|
||
TenantAppUUID = UUIDField(primary_key=True, verbose_name='企业安装记录UUID')
|
||
TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
InstallStatus = models.SmallIntegerField(default=1, verbose_name='安装状态(1启用/0停用)')
|
||
AuthStatus = models.SmallIntegerField(default=0, verbose_name='企业授权状态(0/1/2)')
|
||
InstallTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='安装时间')
|
||
AuthBy = UUIDField(null=True, blank=True, verbose_name='企业授权人UUID')
|
||
AuthTime = models.DateTimeField(null=True, blank=True, verbose_name='企业授权时间')
|
||
|
||
class Meta:
|
||
db_table = 'TenantApp'
|
||
app_label = 'gerp'
|
||
unique_together = (('TenantUUID', 'AppUUID'),)
|
||
verbose_name = '企业应用安装'
|
||
verbose_name_plural = '企业应用安装'
|
||
|
||
|
||
class CompanyApp(QModel):
|
||
"""企业应用安装(公司维度)——租户可选定一个或多个公司安装
|
||
|
||
与 TenantApp(租户全量安装)互补:
|
||
- TenantApp :全租户安装(租户下所有公司生效)
|
||
- CompanyApp :指定公司安装(仅该公司生效)
|
||
企业功能可用判定:租户已装 或 当前公司已装。
|
||
|
||
InstallStatus: 1=已安装(启用) / 0=已停用
|
||
"""
|
||
|
||
CompanyAppUUID = UUIDField(primary_key=True, verbose_name='公司安装记录UUID')
|
||
TenantUUID = UUIDField(null=True, blank=True, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
InstallStatus = models.SmallIntegerField(default=1, verbose_name='安装状态(1启用/0停用)')
|
||
InstallTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='安装时间')
|
||
InstallBy = UUIDField(null=True, blank=True, verbose_name='安装人UUID')
|
||
|
||
class Meta:
|
||
db_table = 'CompanyApp'
|
||
app_label = 'gerp'
|
||
unique_together = (('CompanyUUID', 'AppUUID'),)
|
||
verbose_name = '企业应用安装(公司维度)'
|
||
verbose_name_plural = '企业应用安装(公司维度)'
|
||
|
||
|
||
class UserApp(QModel):
|
||
"""个人应用启用/申请——用户与应用的关系
|
||
|
||
UseStatus: 1=已启用 / 0=已停用
|
||
ApplyStatus: 0=未申请 / 1=申请中 / 2=已授权 / 3=已拒绝(AuthRequired 应用的审批状态机)
|
||
"""
|
||
|
||
UserAppUUID = UUIDField(primary_key=True, verbose_name='个人应用记录UUID')
|
||
UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID')
|
||
AppUUID = UUIDField(db_column='AppUUID', verbose_name='应用UUID')
|
||
UseStatus = models.SmallIntegerField(default=1, verbose_name='使用状态(1启用/0停用)')
|
||
ApplyStatus = models.SmallIntegerField(default=0, verbose_name='授权申请状态(0/1/2/3)')
|
||
ApplyTime = models.DateTimeField(null=True, blank=True, verbose_name='申请时间')
|
||
ApproveBy = UUIDField(null=True, blank=True, verbose_name='审批人UUID')
|
||
ApproveTime = models.DateTimeField(null=True, blank=True, verbose_name='审批时间')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'UserApp'
|
||
app_label = 'gerp'
|
||
unique_together = (('UserUUID', 'AppUUID'),)
|
||
verbose_name = '个人应用'
|
||
verbose_name_plural = '个人应用'
|
||
|
||
|
||
class AppConfig(QModel):
|
||
"""应用配置键值表——按租户+公司隔离的应用运行参数
|
||
|
||
每个应用可声明若干配置键(ConfigKey),管理员(有安装权限者)可在
|
||
"配置"入口编辑配置值。
|
||
|
||
配置粒度:
|
||
- CompanyUUID 为空(NULL)→ 租户级默认配置(全租户共享)
|
||
- CompanyUUID 有值 → 公司级配置(覆盖租户默认)
|
||
读取回退链:公司级 → 租户默认 → 模板默认值。
|
||
|
||
键值约定(由应用自行定义与读取,如 [stalwart] 域名映射):
|
||
ConfigKey 配置键名(如 SMTP_HOST / DOMAIN)
|
||
ConfigValue 配置值(自由文本)
|
||
"""
|
||
|
||
AppConfigUUID = UUIDField(primary_key=True, 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, verbose_name='配置键名')
|
||
ConfigValue = models.TextField(default='', verbose_name='配置值')
|
||
UpdateBy = UUIDField(null=True, blank=True, verbose_name='更新人UUID')
|
||
UpdateTime = models.DateTimeField(null=True, blank=True, verbose_name='更新时间')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'AppConfig'
|
||
app_label = 'gerp'
|
||
unique_together = (('TenantUUID', 'CompanyUUID', 'AppUUID', 'ConfigKey'),)
|
||
verbose_name = '应用配置'
|
||
verbose_name_plural = '应用配置'
|
||
|
||
|
||
class AppPosition(QModel):
|
||
"""应用与职务绑定——只有绑定职务的用户才可见/使用该应用
|
||
|
||
机制:
|
||
- 无该应用的绑定记录(AppPosition 为空)→ 全员可见(兼容存量应用)
|
||
- 有绑定记录 → 用户职务集合(UserRoleDepartmentPosition)必须与绑定职务有交集
|
||
生效点:应用市场 / 我的应用 / 菜单注入 / API 硬校验(AppManager.ListMarket/ListMyApps/CheckAccess)。
|
||
"""
|
||
|
||
AppPositionUUID = UUIDField(primary_key=True, 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, verbose_name='租户UUID')
|
||
CompanyUUID = UUIDField(null=True, blank=True, db_column='CompanyUUID', verbose_name='公司UUID')
|
||
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
|
||
|
||
class Meta:
|
||
db_table = 'AppPosition'
|
||
app_label = 'gerp'
|
||
unique_together = (('AppUUID', 'PositionUUID'),)
|
||
verbose_name = '应用职务绑定'
|
||
verbose_name_plural = '应用职务绑定'
|
||
|
||
|
||
# (AppVirtualPosition / AppVirtualBinding 已下沉 gvsdsdk.models —— 应用虚拟职务统一由 SDK 提供)
|
||
|
||
|
||
# ==================== 从 gvsdsdk 导入重复模型 ====================
|
||
# 以下模型原在 gerp/models.py 定义,现统一依赖 gvsdsdk.models(映射 SAAS 主库)
|
||
# 注意:gvsdsdk 模型用 UUIDField(非 ForeignKey),不支持 select_related 和关联属性访问
|
||
from gvsdsdk.models import (
|
||
Role, Permission, UserRole, RolePermission,
|
||
UserABAC, Password, PositionTemplate, PositionTemplatePermission,
|
||
Position, PositionCategory, UserRoleDepartmentPosition,
|
||
AppVirtualPosition, AppVirtualBinding,
|
||
)
|
||
|
||
|
||
# ==================== GERP 子应用模型(新目录规约) ====================
|
||
# 子应用模型定义在 gvsds/apps/gerp/apps/<appenglishid>/models.py(app_label 仍为 'gerp',
|
||
# 迁移仍集中在 gerp/migrations/)。此处在模型加载阶段统一导入并把公开名字注入本模块命名空间:
|
||
# 1) 保证 Django 加载 gerp.models 时即完成子应用模型注册
|
||
# 2) 保持历史导入路径 `from gvsds.apps.gerp.models import X` 继续可用
|
||
#
|
||
# 约定:子应用模型若要引用本模块的平台模型,必须用绝对导入
|
||
# `from gvsds.apps.gerp.models import X`;子应用包不得在 import 时导入模型
|
||
# (否则会在 app 注册阶段触发 AppRegistryNotReady)。
|
||
from .apps import ImportSubAppModels as _ImportSubAppModels # noqa: E402
|
||
|
||
_ImportSubAppModels(namespace=globals())
|