feat: add tenant-scoped rule and permission management
This commit is contained in:
@@ -26,6 +26,8 @@ from fastapi_modules.fastapi_leaudit.domian.vo.contractTemplateVo import (
|
||||
ContractTemplateSearchResultVO,
|
||||
)
|
||||
from fastapi_modules.fastapi_leaudit.services.contractTemplateService import IContractTemplateService
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ssoUserCompat import SsoUserCompat
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.tenantResolver import TenantResolver
|
||||
from fastapi_modules.fastapi_leaudit.services.ossService import IOssService
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServiceImpl
|
||||
|
||||
@@ -40,8 +42,9 @@ _ALLOWED_SORT_FIELDS = {
|
||||
class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
"""合同模板服务实现。"""
|
||||
|
||||
def __init__(self, OssService: IOssService | None = None) -> None:
|
||||
def __init__(self, OssService: IOssService | None = None, TenantResolverService: TenantResolver | None = None) -> None:
|
||||
self.OssService = OssService or OssServiceImpl()
|
||||
self.TenantResolver = TenantResolverService or TenantResolver()
|
||||
|
||||
async def ListCategories(self, IncludeDisabled: bool, WithTemplateCount: bool) -> list[ContractTemplateCategoryVO]:
|
||||
count_select = "COUNT(t.id)::int AS template_count" if WithTemplateCount else "0::int AS template_count"
|
||||
@@ -83,6 +86,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
category_id=Query.category_id,
|
||||
category_name=Query.category_name,
|
||||
region=Query.region,
|
||||
tenant_code=Query.tenant_code,
|
||||
file_format=Query.file_format,
|
||||
is_featured=Query.is_featured,
|
||||
currentUser=currentUser,
|
||||
@@ -111,6 +115,14 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
c.icon AS category_icon,
|
||||
c.description AS category_description,
|
||||
t.region,
|
||||
COALESCE(NULLIF(BTRIM(t.tenant_code), ''), NULL) AS tenant_code,
|
||||
CASE
|
||||
WHEN t.tenant_name IS NOT NULL AND BTRIM(t.tenant_name) <> '' THEN t.tenant_name
|
||||
WHEN t.region IS NOT NULL AND BTRIM(t.region) <> '' THEN t.region
|
||||
WHEN NULLIF(BTRIM(t.tenant_code), '') = 'PUBLIC' THEN '公共'
|
||||
WHEN NULLIF(BTRIM(t.tenant_code), '') = 'PROVINCIAL' THEN '省级'
|
||||
ELSE NULL
|
||||
END AS tenant_name,
|
||||
t.description,
|
||||
t.file_path,
|
||||
t.pdf_file_path,
|
||||
@@ -148,13 +160,14 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
category_id=Query.category_id,
|
||||
category_name=Query.category_name,
|
||||
region=Query.region,
|
||||
tenant_code=Query.tenant_code,
|
||||
page=Query.page,
|
||||
page_size=Query.page_size,
|
||||
sort_by=Query.sort_by,
|
||||
sort_order=Query.sort_order,
|
||||
)
|
||||
page_result = await self.ListTemplates(list_query, CurrentUserId)
|
||||
category_stats = await self._load_search_category_stats(Query.q, Query.region, CurrentUserId)
|
||||
category_stats = await self._load_search_category_stats(Query.q, Query.region, Query.tenant_code, CurrentUserId)
|
||||
|
||||
return ContractTemplateSearchResultVO(
|
||||
total=page_result.total,
|
||||
@@ -182,6 +195,14 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
c.icon AS category_icon,
|
||||
c.description AS category_description,
|
||||
t.region,
|
||||
COALESCE(NULLIF(BTRIM(t.tenant_code), ''), NULL) AS tenant_code,
|
||||
CASE
|
||||
WHEN t.tenant_name IS NOT NULL AND BTRIM(t.tenant_name) <> '' THEN t.tenant_name
|
||||
WHEN t.region IS NOT NULL AND BTRIM(t.region) <> '' THEN t.region
|
||||
WHEN NULLIF(BTRIM(t.tenant_code), '') = 'PUBLIC' THEN '公共'
|
||||
WHEN NULLIF(BTRIM(t.tenant_code), '') = 'PROVINCIAL' THEN '省级'
|
||||
ELSE NULL
|
||||
END AS tenant_name,
|
||||
t.description,
|
||||
t.file_path,
|
||||
t.pdf_file_path,
|
||||
@@ -252,7 +273,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
async with GetAsyncSession() as session:
|
||||
await self._ensureContractTemplateSchema(session)
|
||||
currentUser = await self._getCurrentUserContext(CurrentUserId, session)
|
||||
resolvedRegion = self._resolve_upload_region(currentUser, Body.region)
|
||||
resolvedTenantCode, resolvedTenantName, resolvedRegion = self._resolve_upload_scope(currentUser, Body.region, Body.tenant_code)
|
||||
categoryRow = (
|
||||
await session.execute(
|
||||
text(
|
||||
@@ -276,17 +297,23 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
"""
|
||||
SELECT id
|
||||
FROM contract_templates
|
||||
WHERE region = :region
|
||||
WHERE (
|
||||
tenant_code = :tenant_code
|
||||
OR (
|
||||
(tenant_code IS NULL OR BTRIM(tenant_code) = '')
|
||||
AND region = :region
|
||||
)
|
||||
)
|
||||
AND template_code = :template_code
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"region": resolvedRegion, "template_code": normalizedCode},
|
||||
{"tenant_code": resolvedTenantCode, "region": resolvedRegion, "template_code": normalizedCode},
|
||||
)
|
||||
).mappings().first()
|
||||
if duplicateRow:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_409_CONFLICT, f"当前地区已存在模板编码 {normalizedCode}")
|
||||
raise LeauditException(StatusCodeEnum.HTTP_409_CONFLICT, f"当前租户已存在模板编码 {normalizedCode}")
|
||||
|
||||
categoryName = str(categoryRow["name"] or "未分类")
|
||||
objectKey = OssPathUtils.BuildContractTemplateKey(
|
||||
@@ -325,6 +352,8 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
template_code,
|
||||
title,
|
||||
category_id,
|
||||
tenant_code,
|
||||
tenant_name,
|
||||
region,
|
||||
description,
|
||||
file_path,
|
||||
@@ -343,6 +372,8 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
:template_code,
|
||||
:title,
|
||||
:category_id,
|
||||
:tenant_code,
|
||||
:tenant_name,
|
||||
:region,
|
||||
:description,
|
||||
:file_path,
|
||||
@@ -365,6 +396,8 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
"template_code": normalizedCode,
|
||||
"title": normalizedTitle,
|
||||
"category_id": Body.category_id,
|
||||
"tenant_code": resolvedTenantCode,
|
||||
"tenant_name": resolvedTenantName,
|
||||
"region": resolvedRegion,
|
||||
"description": (Body.description or "").strip() or None,
|
||||
"file_path": filePath,
|
||||
@@ -433,6 +466,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
self,
|
||||
keyword: str,
|
||||
requestedRegion: str | None,
|
||||
requestedTenantCode: str | None,
|
||||
CurrentUserId: int,
|
||||
) -> list[ContractTemplateSearchCategoryVO]:
|
||||
clean_keyword = (keyword or "").strip()
|
||||
@@ -443,7 +477,12 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
await self._ensureContractTemplateSchema(session)
|
||||
currentUser = await self._getCurrentUserContext(CurrentUserId, session)
|
||||
params: dict[str, Any] = {"keyword": f"%{clean_keyword}%"}
|
||||
scope_filters = self._build_template_scope_filters(currentUser, params, requestedRegion=requestedRegion)
|
||||
scope_filters = self._build_template_scope_filters(
|
||||
currentUser,
|
||||
params,
|
||||
requestedRegion=requestedRegion,
|
||||
requestedTenantCode=requestedTenantCode,
|
||||
)
|
||||
filters = [
|
||||
"c.deleted_at IS NULL",
|
||||
"t.deleted_at IS NULL",
|
||||
@@ -487,6 +526,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
category_id: int | None,
|
||||
category_name: str | None,
|
||||
region: str | None,
|
||||
tenant_code: str | None,
|
||||
file_format: str | None,
|
||||
is_featured: bool | None,
|
||||
currentUser: dict[str, Any],
|
||||
@@ -495,7 +535,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
params: dict[str, Any] = {}
|
||||
needs_category_name_filter = False
|
||||
|
||||
filters.extend(self._build_template_scope_filters(currentUser, params, region))
|
||||
filters.extend(self._build_template_scope_filters(currentUser, params, region, tenant_code))
|
||||
|
||||
if category_id is not None:
|
||||
filters.append("t.category_id = :category_id")
|
||||
@@ -543,6 +583,8 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
def _bind_expanding(self, *sql_objects_and_params: Any):
|
||||
sql_objects = list(sql_objects_and_params[:-1])
|
||||
params = sql_objects_and_params[-1]
|
||||
if "visible_tenant_codes" in params:
|
||||
sql_objects = [sql.bindparams(bindparam("visible_tenant_codes", expanding=True)) for sql in sql_objects]
|
||||
if "visible_regions" in params:
|
||||
sql_objects = [sql.bindparams(bindparam("visible_regions", expanding=True)) for sql in sql_objects]
|
||||
return tuple(sql_objects)
|
||||
@@ -559,6 +601,7 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
)
|
||||
|
||||
def _to_list_item_vo(self, row: Any) -> ContractTemplateListItemVO:
|
||||
tenant_name = row.get("tenant_name") or row.get("region") or "省级"
|
||||
return ContractTemplateListItemVO(
|
||||
id=int(row["id"]),
|
||||
template_code=str(row.get("template_code") or ""),
|
||||
@@ -567,7 +610,9 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
category_name=row.get("category_name"),
|
||||
category_icon=row.get("category_icon"),
|
||||
description=row.get("description"),
|
||||
region=str(row.get("region") or "省级"),
|
||||
region=str(row.get("region") or tenant_name),
|
||||
tenant_code=row.get("tenant_code"),
|
||||
tenant_name=tenant_name,
|
||||
file_path=row.get("file_path"),
|
||||
pdf_file_path=row.get("pdf_file_path"),
|
||||
file_format=str(row.get("file_format") or ""),
|
||||
@@ -600,53 +645,145 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
currentUser: dict[str, Any],
|
||||
params: dict[str, Any],
|
||||
requestedRegion: str | None,
|
||||
requestedTenantCode: str | None = None,
|
||||
writable: bool = False,
|
||||
) -> list[str]:
|
||||
requested = (requestedRegion or "").strip()
|
||||
area = str(currentUser["area"] or "").strip()
|
||||
requested_tenant_code, requested_region = self._normalize_scope_value(requestedRegion, requestedTenantCode, currentUser)
|
||||
current_tenant_code = str(currentUser.get("tenant_code") or "").strip()
|
||||
current_region = str(currentUser["tenant_scope_value"] or currentUser["area"] or "").strip()
|
||||
|
||||
if currentUser["is_global"]:
|
||||
if requested:
|
||||
params["requested_region"] = requested
|
||||
if requested_tenant_code:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=requested_tenant_code,
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if requested_region:
|
||||
if requested_region in {"省级", "公共"}:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code="PROVINCIAL" if requested_region == "省级" else "PUBLIC",
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
params["requested_region"] = requested_region
|
||||
return ["t.region = :requested_region"]
|
||||
return ["1=1"]
|
||||
|
||||
if writable:
|
||||
if not area:
|
||||
if not current_tenant_code and not current_region:
|
||||
return ["1=0"]
|
||||
if requested and requested != area:
|
||||
if requested_tenant_code and current_tenant_code and requested_tenant_code != current_tenant_code:
|
||||
return ["1=0"]
|
||||
params["scope_region"] = area
|
||||
return ["t.region = :scope_region"]
|
||||
if requested_tenant_code and not current_tenant_code:
|
||||
return ["1=0"]
|
||||
if requested_region and requested_region != current_region:
|
||||
return ["1=0"]
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=current_tenant_code or requested_tenant_code,
|
||||
region=current_region or requested_region,
|
||||
prefix="scope",
|
||||
)
|
||||
|
||||
if currentUser["can_manage"]:
|
||||
if not area:
|
||||
if not current_tenant_code and not current_region:
|
||||
return ["1=0"]
|
||||
if requested:
|
||||
if requested == "省级":
|
||||
params["requested_region"] = requested
|
||||
return ["t.region = :requested_region"]
|
||||
if requested != area:
|
||||
if requested_tenant_code:
|
||||
if requested_tenant_code in {"PUBLIC", "PROVINCIAL"}:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=requested_tenant_code,
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if current_tenant_code and requested_tenant_code != current_tenant_code:
|
||||
return ["1=0"]
|
||||
params["requested_region"] = requested
|
||||
return ["t.region = :requested_region"]
|
||||
params["visible_regions"] = ["省级", area]
|
||||
return ["t.region IN :visible_regions"]
|
||||
if not current_tenant_code and requested_region != current_region:
|
||||
return ["1=0"]
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=requested_tenant_code,
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if requested_region:
|
||||
if requested_region in {"省级", "公共"}:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code="PROVINCIAL" if requested_region == "省级" else "PUBLIC",
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if requested_region != current_region:
|
||||
return ["1=0"]
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=current_tenant_code or None,
|
||||
region=current_region or requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
return self._visible_tenant_filters(
|
||||
params,
|
||||
tenant_codes=["PROVINCIAL", "PUBLIC", current_tenant_code],
|
||||
legacy_regions=["省级", "公共", current_region],
|
||||
)
|
||||
|
||||
if requested:
|
||||
if requested == "省级":
|
||||
params["requested_region"] = requested
|
||||
return ["t.region = :requested_region"]
|
||||
if area and requested == area:
|
||||
params["requested_region"] = requested
|
||||
return ["t.region = :requested_region"]
|
||||
if requested_tenant_code:
|
||||
if requested_tenant_code in {"PUBLIC", "PROVINCIAL"}:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=requested_tenant_code,
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if current_tenant_code and requested_tenant_code == current_tenant_code:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=requested_tenant_code,
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if not current_tenant_code and current_region and requested_region == current_region:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=requested_tenant_code,
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
return ["1=0"]
|
||||
|
||||
if area:
|
||||
params["visible_regions"] = ["省级", area]
|
||||
return ["t.region IN :visible_regions"]
|
||||
params["requested_region"] = "省级"
|
||||
return ["t.region = :requested_region"]
|
||||
if requested_region:
|
||||
if requested_region in {"省级", "公共"}:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code="PROVINCIAL" if requested_region == "省级" else "PUBLIC",
|
||||
region=requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
if current_region and requested_region == current_region:
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code=current_tenant_code or None,
|
||||
region=current_region or requested_region,
|
||||
prefix="requested",
|
||||
)
|
||||
return ["1=0"]
|
||||
|
||||
if current_tenant_code or current_region:
|
||||
return self._visible_tenant_filters(
|
||||
params,
|
||||
tenant_codes=["PROVINCIAL", "PUBLIC", current_tenant_code],
|
||||
legacy_regions=["省级", "公共", current_region],
|
||||
)
|
||||
return self._tenant_filter_sql(
|
||||
params,
|
||||
tenant_code="PROVINCIAL",
|
||||
region="省级",
|
||||
prefix="requested",
|
||||
)
|
||||
|
||||
async def _getCurrentUserContext(self, CurrentUserId: int, session=None) -> dict[str, Any]:
|
||||
own_session = False
|
||||
@@ -655,13 +792,28 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
session_cm = GetAsyncSession()
|
||||
session = await session_cm.__aenter__()
|
||||
try:
|
||||
sso_user_columns = await SsoUserCompat.get_columns(session)
|
||||
tenant_code_select = SsoUserCompat.optional_coalesce_as(
|
||||
sso_user_columns,
|
||||
alias="u",
|
||||
column="tenant_code",
|
||||
fallback_sql="''",
|
||||
)
|
||||
tenant_name_select = SsoUserCompat.optional_coalesce_as(
|
||||
sso_user_columns,
|
||||
alias="u",
|
||||
column="tenant_name",
|
||||
fallback_sql="''",
|
||||
)
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
f"""
|
||||
SELECT
|
||||
u.id,
|
||||
COALESCE(u.area, '') AS area,
|
||||
{tenant_code_select},
|
||||
{tenant_name_select},
|
||||
COALESCE(bool_or(r.role_key IN ('super_admin', 'provincial_admin')), FALSE) AS is_global,
|
||||
COALESCE(bool_or(r.role_key IN ('super_admin', 'provincial_admin', 'admin')), FALSE) AS can_manage
|
||||
FROM sso_users u
|
||||
@@ -676,9 +828,18 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
).mappings().first()
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "当前用户不存在")
|
||||
tenant = await self.TenantResolver.ResolveUserContext(
|
||||
Area=str(row["area"] or ""),
|
||||
TenantCode=str(row.get("tenant_code") or "") or None,
|
||||
TenantName=str(row.get("tenant_name") or "") or None,
|
||||
Source="contract_template_user_context",
|
||||
)
|
||||
return {
|
||||
"id": int(row["id"]),
|
||||
"area": str(row["area"] or ""),
|
||||
"tenant_code": tenant.tenant_code or (str(row.get("tenant_code") or "") or None),
|
||||
"tenant_name": tenant.tenant_name or (str(row.get("tenant_name") or "") or None),
|
||||
"tenant_scope_value": tenant.tenant_name or tenant.normalized_value or str(row["area"] or ""),
|
||||
"is_global": bool(row["is_global"]),
|
||||
"can_manage": bool(row["can_manage"]),
|
||||
"is_area_admin": bool(row["can_manage"]) and not bool(row["is_global"]),
|
||||
@@ -687,14 +848,104 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
if own_session:
|
||||
await session_cm.__aexit__(None, None, None)
|
||||
|
||||
def _resolve_upload_region(self, currentUser: dict[str, Any], requestedRegion: str | None) -> str:
|
||||
_ = requestedRegion
|
||||
area = str(currentUser["area"] or "").strip()
|
||||
def _resolve_upload_scope(
|
||||
self,
|
||||
currentUser: dict[str, Any],
|
||||
requestedRegion: str | None,
|
||||
requestedTenantCode: str | None = None,
|
||||
) -> tuple[str | None, str | None, str]:
|
||||
requested_tenant_code, requested_region = self._normalize_scope_value(requestedRegion, requestedTenantCode, currentUser)
|
||||
current_tenant_code = str(currentUser.get("tenant_code") or "").strip() or None
|
||||
current_tenant_name = str(currentUser.get("tenant_name") or "").strip() or None
|
||||
current_region = str(currentUser["tenant_scope_value"] or currentUser["area"] or "").strip()
|
||||
if not currentUser.get("is_area_admin"):
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前仅支持地区管理员上传合同模板")
|
||||
if not area:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前地区管理员账号未配置所属地区,无法上传合同模板")
|
||||
return area
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前仅支持租户管理员上传合同模板")
|
||||
if not current_region:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前租户管理员账号未配置所属租户,无法上传合同模板")
|
||||
if requested_tenant_code and not current_tenant_code:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前账号未配置标准租户编码,不能显式指定模板租户")
|
||||
if requested_tenant_code and current_tenant_code and requested_tenant_code != current_tenant_code:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前仅允许上传到本人所属租户")
|
||||
if requested_region and requested_region != current_region:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_403_FORBIDDEN, "当前仅允许上传到本人所属租户")
|
||||
return current_tenant_code, current_tenant_name or current_region, current_region
|
||||
|
||||
def _normalize_scope_value(
|
||||
self,
|
||||
requestedRegion: str | None,
|
||||
requestedTenantCode: str | None,
|
||||
currentUser: dict[str, Any],
|
||||
) -> tuple[str | None, str]:
|
||||
tenant_code = str(requestedTenantCode or "").strip()
|
||||
if tenant_code:
|
||||
if tenant_code == str(currentUser.get("tenant_code") or "").strip():
|
||||
return (
|
||||
str(currentUser.get("tenant_code") or "").strip() or None,
|
||||
str(currentUser.get("tenant_scope_value") or currentUser.get("tenant_name") or currentUser.get("area") or "").strip(),
|
||||
)
|
||||
if tenant_code == "PUBLIC":
|
||||
return "PUBLIC", "公共"
|
||||
if tenant_code == "PROVINCIAL":
|
||||
return "PROVINCIAL", "省级"
|
||||
return tenant_code, str(requestedRegion or "").strip()
|
||||
return None, str(requestedRegion or "").strip()
|
||||
|
||||
def _tenant_filter_sql(
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
*,
|
||||
tenant_code: str | None,
|
||||
region: str | None,
|
||||
prefix: str,
|
||||
) -> list[str]:
|
||||
normalized_region = str(region or "").strip()
|
||||
normalized_tenant_code = str(tenant_code or "").strip()
|
||||
if normalized_tenant_code:
|
||||
params[f"{prefix}_tenant_code"] = normalized_tenant_code
|
||||
if normalized_region:
|
||||
params[f"{prefix}_region"] = normalized_region
|
||||
return [
|
||||
"("
|
||||
f"t.tenant_code = :{prefix}_tenant_code "
|
||||
"OR ("
|
||||
"(t.tenant_code IS NULL OR BTRIM(t.tenant_code) = '') "
|
||||
f"AND t.region = :{prefix}_region"
|
||||
")"
|
||||
")"
|
||||
]
|
||||
return [f"t.tenant_code = :{prefix}_tenant_code"]
|
||||
if normalized_region:
|
||||
params[f"{prefix}_region"] = normalized_region
|
||||
return [f"t.region = :{prefix}_region"]
|
||||
return ["1=0"]
|
||||
|
||||
def _visible_tenant_filters(
|
||||
self,
|
||||
params: dict[str, Any],
|
||||
*,
|
||||
tenant_codes: list[str | None],
|
||||
legacy_regions: list[str | None],
|
||||
) -> list[str]:
|
||||
normalized_tenant_codes = [code.strip() for code in tenant_codes if code and str(code).strip()]
|
||||
normalized_regions = [region.strip() for region in legacy_regions if region and str(region).strip()]
|
||||
if normalized_tenant_codes:
|
||||
params["visible_tenant_codes"] = normalized_tenant_codes
|
||||
if normalized_regions:
|
||||
params["visible_regions"] = normalized_regions
|
||||
return [
|
||||
"("
|
||||
"t.tenant_code IN :visible_tenant_codes "
|
||||
"OR ("
|
||||
"(t.tenant_code IS NULL OR BTRIM(t.tenant_code) = '') "
|
||||
"AND t.region IN :visible_regions"
|
||||
")"
|
||||
")"
|
||||
]
|
||||
return ["t.tenant_code IN :visible_tenant_codes"]
|
||||
if normalized_regions:
|
||||
params["visible_regions"] = normalized_regions
|
||||
return ["t.region IN :visible_regions"]
|
||||
return ["1=0"]
|
||||
|
||||
async def _ensureContractTemplateSchema(self, session) -> None:
|
||||
statements = [
|
||||
@@ -716,6 +967,14 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
""",
|
||||
"""
|
||||
ALTER TABLE contract_templates
|
||||
ADD COLUMN IF NOT EXISTS tenant_code VARCHAR(64)
|
||||
""",
|
||||
"""
|
||||
ALTER TABLE contract_templates
|
||||
ADD COLUMN IF NOT EXISTS tenant_name VARCHAR(128)
|
||||
""",
|
||||
"""
|
||||
ALTER TABLE contract_templates
|
||||
ADD COLUMN IF NOT EXISTS pdf_file_path VARCHAR(500)
|
||||
""",
|
||||
"""
|
||||
@@ -758,6 +1017,17 @@ class ContractTemplateServiceImpl(IContractTemplateService):
|
||||
"""
|
||||
)
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE contract_templates
|
||||
SET tenant_name = region
|
||||
WHERE (tenant_name IS NULL OR BTRIM(tenant_name) = '')
|
||||
AND region IS NOT NULL
|
||||
AND BTRIM(region) <> ''
|
||||
"""
|
||||
)
|
||||
)
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user