feat: add tenant-scoped rule and permission management

This commit is contained in:
wren
2026-05-21 22:03:08 +08:00
parent a2c2bf1969
commit 1f1bccf3b3
193 changed files with 64463 additions and 1771 deletions
@@ -7,7 +7,8 @@ class ContractTemplateListQueryDTO(BaseModel):
keyword: str | None = Field(None, description="关键词")
category_id: int | None = Field(None, description="分类ID")
category_name: str | None = Field(None, description="分类名称")
region: str | None = Field(None, description="地区")
region: str | None = Field(None, description="兼容保留字段:租户展示值/旧地区")
tenant_code: str | None = Field(None, description="租户编码")
file_format: str | None = Field(None, description="文件格式")
is_featured: bool | None = Field(None, description="是否推荐")
page: int = Field(1, ge=1, description="页码")
@@ -22,7 +23,8 @@ class ContractTemplateSearchQueryDTO(BaseModel):
q: str = Field(..., min_length=1, description="搜索关键词")
category_id: int | None = Field(None, description="分类ID")
category_name: str | None = Field(None, description="分类名称")
region: str | None = Field(None, description="地区")
region: str | None = Field(None, description="兼容保留字段:租户展示值/旧地区")
tenant_code: str | None = Field(None, description="租户编码")
page: int = Field(1, ge=1, description="页码")
page_size: int = Field(12, ge=1, le=200, description="分页大小")
sort_by: str = Field("updated_at", description="排序字段")
@@ -35,6 +37,7 @@ class ContractTemplateCreateDTO(BaseModel):
title: str = Field(..., min_length=1, max_length=200, description="模板标题")
template_code: str = Field(..., min_length=1, max_length=50, description="模板编码")
category_id: int = Field(..., description="分类ID")
region: str | None = Field(None, description="所属地区")
region: str | None = Field(None, description="兼容保留字段:所属租户展示值/旧地区")
tenant_code: str | None = Field(None, description="所属租户编码")
description: str | None = Field(None, description="模板简介")
is_featured: bool = Field(False, description="是否推荐")
@@ -4,13 +4,22 @@ from pydantic import BaseModel, Field
class EntryModuleAreaDTO(BaseModel):
"""入口模块地区配置。"""
"""入口模块历史地区配置,仅用于兼容旧请求"""
area: str = Field(..., description="地区名称")
enabled: bool = Field(True, description="是否启用")
sort_order: int = Field(0, description="排序号")
class EntryModuleTenantDTO(BaseModel):
"""入口模块租户配置。"""
tenant_code: str = Field(..., description="租户编码")
tenant_name: str | None = Field(None, description="租户名称")
enabled: bool = Field(True, description="是否启用")
sort_order: int = Field(0, description="排序号")
class EntryModuleCreateDTO(BaseModel):
"""创建入口模块请求。"""
@@ -18,7 +27,8 @@ class EntryModuleCreateDTO(BaseModel):
description: str | None = Field(None, description="模块描述")
path: str | None = Field(None, description="前端路由路径")
route_path: str | None = Field(None, description="前端跳转路径")
areas: list[EntryModuleAreaDTO] | None = Field(None, description="地区配置")
areas: list[EntryModuleAreaDTO] | None = Field(None, description="历史地区配置(兼容字段,建议改用 tenants")
tenants: list[EntryModuleTenantDTO] | None = Field(None, description="租户配置")
class EntryModuleUpdateDTO(BaseModel):
@@ -28,4 +38,5 @@ class EntryModuleUpdateDTO(BaseModel):
description: str | None = Field(None, description="模块描述")
path: str | None = Field(None, description="前端路由路径")
route_path: str | None = Field(None, description="前端跳转路径")
areas: list[EntryModuleAreaDTO] | None = Field(None, description="地区配置")
areas: list[EntryModuleAreaDTO] | None = Field(None, description="历史地区配置(兼容字段,建议改用 tenants")
tenants: list[EntryModuleTenantDTO] | None = Field(None, description="租户配置")
@@ -23,6 +23,8 @@ class EvaluationPointBaseDTO(BaseModel):
action_config: str | None = Field(None, description="动作配置")
score: float | int | None = Field(None, description="分值")
area: str | None = Field(None, description="地区")
tenant_code: str | None = Field(None, description="租户编码")
tenant_name: str | None = Field(None, description="租户名称")
class EvaluationPointCreateDTO(EvaluationPointBaseDTO):
@@ -60,3 +60,9 @@ class UserRolesAssignDTO(BaseModel):
"""用户角色分配请求。"""
role_ids: list[int] = Field(default_factory=list, description="角色ID列表")
class UserTenantUpdateDTO(BaseModel):
"""用户租户更新请求。"""
tenant_code: str = Field(..., description="租户编码")
@@ -9,7 +9,7 @@ class RuleBindingCreateDTO(BaseModel):
docTypeId: int = Field(..., description="文档类型ID → leaudit_document_types.id")
docTypeCode: str | None = Field(None, description="文档类型编码(冗余快速匹配)")
ruleSetId: int = Field(..., description="规则集ID → leaudit_rule_sets.id")
region: str = Field("default", description="适用地区")
region: str = Field("公共", description="兼容保留字段:旧地区/租户展示值")
bindingMode: str = Field("explicit", description="绑定模式: explicit / wildcard / fallback")
priority: int = Field(0, description="优先级(数值越大优先级越高)")
note: str | None = Field(None, description="备注说明")
@@ -0,0 +1,51 @@
"""租户主数据 DTO。"""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, Field
class TenantCreateDTO(BaseModel):
"""创建租户请求。"""
tenant_code: str = Field(..., description="租户编码")
tenant_name: str = Field(..., description="租户名称")
tenant_short_name: str | None = Field(None, description="租户简称")
tenant_type: str = Field("CUSTOM", description="租户类型")
parent_tenant_code: str | None = Field(None, description="父级租户编码")
display_order: int = Field(0, description="显示顺序")
is_enabled: bool = Field(True, description="是否启用")
is_public: bool = Field(False, description="是否公共租户")
can_host_entry_module: bool = Field(True, description="是否可分配入口模块")
can_host_documents: bool = Field(True, description="是否可承载文档")
can_host_rag: bool = Field(True, description="是否可承载知识库")
can_host_templates: bool = Field(True, description="是否可承载模板")
feature_keys: list[str] = Field(default_factory=list, description="功能开关键列表")
alias_values: list[str] = Field(default_factory=list, description="兼容别名列表")
ext: dict[str, Any] | None = Field(None, description="扩展字段")
class TenantUpdateDTO(BaseModel):
"""更新租户请求。"""
tenant_name: str | None = Field(None, description="租户名称")
tenant_short_name: str | None = Field(None, description="租户简称")
tenant_type: str | None = Field(None, description="租户类型")
parent_tenant_code: str | None = Field(None, description="父级租户编码")
display_order: int | None = Field(None, description="显示顺序")
is_public: bool | None = Field(None, description="是否公共租户")
can_host_entry_module: bool | None = Field(None, description="是否可分配入口模块")
can_host_documents: bool | None = Field(None, description="是否可承载文档")
can_host_rag: bool | None = Field(None, description="是否可承载知识库")
can_host_templates: bool | None = Field(None, description="是否可承载模板")
feature_keys: list[str] | None = Field(None, description="功能开关键列表")
alias_values: list[str] | None = Field(None, description="兼容别名列表")
ext: dict[str, Any] | None = Field(None, description="扩展字段")
class TenantStatusUpdateDTO(BaseModel):
"""租户启停请求。"""
is_enabled: bool = Field(..., description="是否启用")
@@ -23,7 +23,9 @@ class ContractTemplateListItemVO(BaseModel):
category_name: str | None = Field(None, description="分类名称")
category_icon: str | None = Field(None, description="分类图标")
description: str | None = Field(None, description="模板简介")
region: str = Field(..., description="所属地区")
region: str = Field(..., description="兼容保留字段:所属租户展示值/旧地区")
tenant_code: str | None = Field(None, description="租户编码")
tenant_name: str | None = Field(None, description="租户名称")
file_path: str | None = Field(None, description="原始模板文件路径")
pdf_file_path: str | None = Field(None, description="PDF 预览文件路径")
file_format: str = Field(..., description="文件格式")
@@ -7,6 +7,13 @@ from datetime import datetime
from pydantic import BaseModel, Field
class CrossReviewTaskTenantVO(BaseModel):
"""任务评查租户展示项。"""
tenantCode: str = Field("", description="租户编码")
tenantName: str = Field("", description="租户名称")
class CrossReviewTaskItemVO(BaseModel):
"""任务列表项。"""
@@ -20,7 +27,8 @@ class CrossReviewTaskItemVO(BaseModel):
totalDocuments: int = Field(0, description="文档总数")
completedDocuments: int = Field(0, description="已完成文档数")
createdAt: datetime | None = Field(None, description="创建时间")
evaluationRegion: list[str] = Field(default_factory=list, description="评查地区")
evaluationTenants: list[CrossReviewTaskTenantVO] = Field(default_factory=list, description="评查租户列表")
evaluationRegion: list[str] = Field(default_factory=list, description="评查租户/地区(兼容展示值)")
class CrossReviewTaskPageVO(BaseModel):
@@ -3,6 +3,7 @@
from pydantic import BaseModel, Field
from fastapi_modules.fastapi_leaudit.domian.vo.auditVo import AuditRunVO
from fastapi_modules.fastapi_leaudit.domian.vo.pageQualityVo import PageQualitySummaryVO
class DocumentUploadVO(BaseModel):
@@ -20,11 +21,16 @@ class DocumentUploadVO(BaseModel):
typeCode: str = Field(..., description="文档类型编码")
groupId: int | None = Field(None, description="命中的二级分组ID")
region: str = Field(..., description="所属地区")
tenantCode: str | None = Field(None, description="所属租户编码")
tenantName: str | None = Field(None, description="所属租户名称")
fileName: str = Field(..., description="文件名")
ossUrl: str = Field(..., description="OSS 对象路径")
speed: str = Field(..., description="执行速度档位:urgent/normal")
processingStatus: str = Field(..., description="文档处理状态")
autoRunTriggered: bool = Field(..., description="是否已自动触发评查")
pageQualityRunId: int | None = Field(None, description="页级模糊检测运行ID")
pageQualityRunStatus: str | None = Field(None, description="页级模糊检测运行状态")
pageQualitySummaryStatus: str | None = Field(None, description="页级模糊检测摘要状态")
run: AuditRunVO | None = Field(None, description="自动触发后的运行信息")
@@ -47,6 +53,11 @@ class DocumentStatusItemVO(BaseModel):
runStatus: str | None = Field(None, description="当前运行状态")
phase: str | None = Field(None, description="当前运行阶段")
resultStatus: str | None = Field(None, description="当前结果状态")
pageQualityRunId: int | None = Field(None, description="页级模糊检测运行ID")
pageQualityRunStatus: str | None = Field(None, description="页级模糊检测运行状态")
pageQualitySummaryStatus: str | None = Field(None, description="页级模糊检测摘要状态")
pageQualityReviewPageCount: int = Field(0, description="疑似模糊页数")
pageQualityRejectPageCount: int = Field(0, description="建议重拍页数")
updatedAt: str | None = Field(None, description="更新时间")
@@ -99,6 +110,8 @@ class DocumentListItemVO(BaseModel):
groupId: int | None = Field(None, description="命中的二级分组ID")
groupName: str | None = Field(None, description="二级分组名称")
region: str = Field(..., description="区域")
tenantCode: str | None = Field(None, description="所属租户编码")
tenantName: str | None = Field(None, description="所属租户名称")
normalizedName: str | None = Field(None, description="归一化名称")
fileId: int | None = Field(None, description="文件ID")
fileName: str | None = Field(None, description="文件名")
@@ -119,6 +132,11 @@ class DocumentListItemVO(BaseModel):
documentNumber: str | None = Field(None, description="业务文号/案号")
auditStatus: int | None = Field(None, description="人工维护审核状态")
isTestDocument: bool = Field(False, description="是否测试文档")
pageQualityRunId: int | None = Field(None, description="页级模糊检测运行ID")
pageQualityRunStatus: str | None = Field(None, description="页级模糊检测运行状态")
pageQualitySummaryStatus: str | None = Field(None, description="页级模糊检测摘要状态")
pageQualityIssueCount: int = Field(0, description="页级问题页数")
pageQualityWarningText: str | None = Field(None, description="页级模糊预警文案")
updatedAt: str | None = Field(None, description="更新时间")
hasHistory: bool = Field(False, description="是否存在历史版本")
totalVersions: int = Field(1, description="总版本数")
@@ -130,6 +148,7 @@ class DocumentDetailVO(DocumentListItemVO):
remark: str | None = Field(None, description="备注")
pageCount: int | None = Field(None, description="页数,暂无精确值时可为空")
pageQualitySummary: PageQualitySummaryVO | None = Field(None, description="页级模糊检测摘要")
attachments: list[DocumentAttachmentVO] = Field(default_factory=list, description="附件列表")
@@ -3,10 +3,11 @@
from pydantic import BaseModel, Field
class EntryModuleAreaVO(BaseModel):
"""入口模块地区配置。"""
class EntryModuleTenantVO(BaseModel):
"""入口模块租户配置。"""
area: str = Field(..., description="地区名称")
tenant_code: str = Field(..., description="租户编码")
tenant_name: str | None = Field(None, description="租户名称")
enabled: bool = Field(True, description="是否启用")
sort_order: int = Field(0, description="排序号")
@@ -21,7 +22,7 @@ class EntryModuleVO(BaseModel):
route_path: str | None = Field(None, description="前端跳转路径")
sort_order: int = Field(0, description="排序")
is_enabled: bool = Field(True, description="是否启用")
areas: list[EntryModuleAreaVO] = Field(default_factory=list, description="地区配置")
tenants: list[EntryModuleTenantVO] = Field(default_factory=list, description="租户配置")
created_at: str | None = Field(None, description="创建时间")
updated_at: str | None = Field(None, description="更新时间")
@@ -13,12 +13,19 @@ class RuleGroupBindingVO(BaseModel):
priority: int = Field(0, description="优先级")
is_active: bool = Field(True, description="是否启用")
note: str | None = Field(None, description="备注")
tenant_code: str | None = Field(None, description="绑定所属租户编码")
scope_type: str | None = Field(None, description="绑定所属作用域")
tenant_name_snapshot: str | None = Field(None, description="绑定所属租户名称快照")
rule_type: str | None = Field(None, description="规则类型编码")
rule_name: str | None = Field(None, description="规则集名称")
current_version_id: int | None = Field(None, description="当前版本ID")
fallback_version_id: int | None = Field(None, description="回退版本ID")
has_usable_version: bool = Field(False, description="是否存在可用版本")
usable_rule_count: int = Field(0, description="可用规则数")
effectiveTenantCode: str | None = Field(None, description="当前绑定实际生效租户编码")
effectiveScopeType: str | None = Field(None, description="当前绑定实际生效作用域")
isInherited: bool = Field(False, description="当前绑定是否为继承态")
sourceRuleSetId: int | None = Field(None, description="来源规则集ID")
class EvaluationPointGroupVO(BaseModel):
@@ -27,6 +27,8 @@ class EvaluationPointVO(BaseModel):
action_config: str = Field("", description="动作配置")
score: float = Field(0, description="分值")
area: str = Field("", description="地区")
tenantCode: str = Field("", description="租户编码")
tenantName: str = Field("", description="租户名称")
created_at: str | None = Field(None, description="创建时间")
updated_at: str | None = Field(None, description="更新时间")
@@ -11,6 +11,15 @@ class HomeEntryAreaVO(BaseModel):
sortOrder: int = Field(0, description="地区内排序")
class HomeEntryTenantVO(BaseModel):
"""入口模块租户配置。"""
tenantCode: str = Field(..., description="租户编码")
tenantName: str | None = Field(None, description="租户名称")
enabled: bool = Field(..., description="是否启用")
sortOrder: int = Field(0, description="租户内排序")
class HomeEntryDocumentTypeVO(BaseModel):
"""入口模块下的文档类型。"""
@@ -31,4 +40,5 @@ class HomeEntryModuleVO(BaseModel):
sortOrder: int = Field(0, description="排序序号")
requiresDocumentTypes: bool = Field(True, description="是否要求至少绑定一个文档类型")
areas: list[HomeEntryAreaVO] = Field(default_factory=list, description="地区配置")
tenants: list[HomeEntryTenantVO] = Field(default_factory=list, description="租户配置")
documentTypes: list[HomeEntryDocumentTypeVO] = Field(default_factory=list, description="关联文档类型列表")
@@ -0,0 +1,41 @@
"""页级图片质量 VO。"""
from pydantic import BaseModel, Field
class PageQualityPageResultVO(BaseModel):
"""单页模糊检测结果。"""
pageNum: int = Field(..., description="页码")
qualityStatus: str = Field(..., description="pass/review/reject")
qualityScore: float | None = Field(None, description="分值")
reasonText: str | None = Field(None, description="原因说明")
class PageQualitySummaryVO(BaseModel):
"""文档页级模糊检测摘要。"""
runId: int | None = Field(None, description="最新运行ID")
runStatus: str | None = Field(None, description="运行状态")
summaryStatus: str | None = Field(None, description="摘要状态")
totalPages: int = Field(0, description="总页数")
reviewPageCount: int = Field(0, description="疑似模糊页数")
rejectPageCount: int = Field(0, description="建议重拍页数")
warningText: str | None = Field(None, description="汇总提示文案")
pages: list[int] = Field(default_factory=list, description="问题页码列表")
finishedAt: str | None = Field(None, description="完成时间")
class PageQualityDetailVO(BaseModel):
"""文档页级模糊检测详情。"""
summary: PageQualitySummaryVO = Field(..., description="摘要")
results: list[PageQualityPageResultVO] = Field(default_factory=list, description="页结果")
class PageQualityRecheckVO(BaseModel):
"""手工重检响应。"""
runId: int = Field(..., description="运行ID")
documentId: int = Field(..., description="文档ID")
status: str = Field(..., description="queued/running")
@@ -5,6 +5,8 @@ class RagChatAppVO(BaseModel):
appId: str = Field(..., description="应用ID")
appName: str = Field(..., description="应用名称")
description: str = Field("", description="应用描述")
tenantCode: str = Field("", description="租户编码")
tenantName: str = Field("", description="租户名称")
isDefault: bool = Field(False, description="是否默认应用")
@@ -6,6 +6,8 @@ class RagDatasetItemVO(BaseModel):
name: str = Field(...)
description: str = Field("")
area: str = Field("")
tenantCode: str = Field("")
tenantName: str = Field("")
isPublic: bool = Field(False)
isDefault: bool = Field(False)
documentCount: int = Field(0)
@@ -29,6 +31,8 @@ class RagDatasetDetailVO(BaseModel):
name: str = Field(...)
description: str = Field("")
area: str = Field("")
tenantCode: str = Field("")
tenantName: str = Field("")
isPublic: bool = Field(False)
isDefault: bool = Field(False)
status: int = Field(1)
@@ -46,6 +46,7 @@ class UserVO(BaseModel):
phone_number: str | None = Field(None, description="手机号")
email: str | None = Field(None, description="邮箱")
area: str | None = Field(None, description="地区")
tenant_code: str | None = Field(None, description="租户编码")
ou_name: str | None = Field(None, description="组织名称")
ou_id: str | None = Field(None, description="组织ID")
status: int = Field(0, description="状态")
@@ -81,6 +82,7 @@ class OrganizationTreeUserVO(BaseModel):
username: str = Field(..., description="用户名")
nick_name: str = Field(..., description="姓名")
area: str | None = Field(None, description="地区")
tenant_code: str | None = Field(None, description="租户编码")
ou_id: str = Field("", description="组织ID")
ou_name: str = Field("", description="组织名称")
is_leader: bool = Field(False, description="是否负责人")
@@ -195,6 +197,16 @@ class UserRolesVO(BaseModel):
roles: list[RoleVO] = Field(default_factory=list, description="角色列表")
class UserTenantUpdateVO(BaseModel):
"""用户租户更新响应。"""
user_id: int = Field(..., description="用户ID")
username: str = Field(..., description="用户名")
area: str | None = Field(None, description="兼容地区展示值")
tenant_code: str | None = Field(None, description="租户编码")
tenant_name: str | None = Field(None, description="租户名称")
class RoutePermissionsVO(BaseModel):
"""路由权限响应。"""
@@ -11,6 +11,10 @@ class RuleConfigPackVO(BaseModel):
rootGroupId: int | None = Field(None, description="一级分组ID")
bindingId: int | None = Field(None, description="当前命中的规则集绑定ID")
ruleSetId: int | None = Field(None, description="命中的规则集ID")
effectiveTenantCode: str | None = Field(None, description="当前命中的生效租户编码")
effectiveScopeType: str | None = Field(None, description="当前命中的生效作用域类型")
isInherited: bool = Field(False, description="当前规则是否来自继承作用域")
sourceRuleSetId: int | None = Field(None, description="来源规则集ID")
ruleType: str | None = Field(None, description="规则类型编码")
ruleName: str | None = Field(None, description="规则集名称")
currentVersionId: int | None = Field(None, description="规则集当前版本ID")
@@ -25,6 +29,7 @@ class RuleConfigPackVO(BaseModel):
subtype: str = Field("", description="二级业务子类型名称")
yamlText: str = Field("", description="当前规则 YAML 正文")
sourceStatus: str = Field(..., description="ready/empty/missing")
rules: list["RuleConfigPackRuleSummaryVO"] = Field(default_factory=list, description="规则摘要列表")
class RuleConfigPackRuleSummaryVO(BaseModel):
@@ -57,6 +62,10 @@ class RuleConfigPackListVO(BaseModel):
rootGroupId: int | None = Field(None, description="一级分组ID")
bindingId: int | None = Field(None, description="当前命中的规则集绑定ID")
ruleSetId: int | None = Field(None, description="命中的规则集ID")
effectiveTenantCode: str | None = Field(None, description="当前命中的生效租户编码")
effectiveScopeType: str | None = Field(None, description="当前命中的生效作用域类型")
isInherited: bool = Field(False, description="当前规则是否来自继承作用域")
sourceRuleSetId: int | None = Field(None, description="来源规则集ID")
ruleType: str | None = Field(None, description="规则类型编码")
ruleName: str | None = Field(None, description="规则集名称")
currentVersionId: int | None = Field(None, description="规则集当前版本ID")
@@ -9,6 +9,10 @@ class RuleSetVO(BaseModel):
id: int = Field(..., description="规则集ID")
ruleType: str = Field(..., description="业务规则类型编码")
ruleName: str = Field(..., description="规则集名称")
effectiveTenantCode: str | None = Field(None, description="当前生效租户编码")
effectiveScopeType: str | None = Field(None, description="当前生效作用域")
isInherited: bool = Field(False, description="当前规则集是否为继承态")
sourceRuleSetId: int | None = Field(None, description="来源规则集ID")
domainType: str | None = Field(None, description="域类型")
currentVersionId: int | None = Field(None, description="当前激活版本ID")
fallbackVersionId: int | None = Field(None, description="最近一个可回退使用的已发布版本ID")
@@ -34,6 +34,8 @@ class UsageStatsUserItemVO(BaseModel):
nickName: str = Field("")
departmentName: str | None = Field(None)
area: str | None = Field(None)
tenantCode: str | None = Field(None)
tenantName: str | None = Field(None)
loginCount: int = Field(0)
uploadDocumentCount: int = Field(0)
uploadAttachmentCount: int = Field(0)
@@ -71,6 +73,8 @@ class UsageStatsDepartmentPageVO(BaseModel):
class UsageStatsAreaItemVO(BaseModel):
area: str = Field("")
tenantCode: str | None = Field(None)
tenantName: str | None = Field(None)
loginUserCount: int = Field(0)
loginCount: int = Field(0)
uploadDocumentCount: int = Field(0)
@@ -96,6 +100,8 @@ class UsageStatsDetailItemVO(BaseModel):
nickName: str = Field("")
departmentName: str | None = Field(None)
area: str | None = Field(None)
tenantCode: str | None = Field(None)
tenantName: str | None = Field(None)
documentId: int | None = Field(None)
documentName: str | None = Field(None)
documentTypeId: int | None = Field(None)