feat: add tenant-scoped rule and permission management
This commit is contained in:
@@ -22,6 +22,7 @@ from fastapi_modules.fastapi_leaudit.leaudit_bridge.ruleValidator import RuleVal
|
||||
from fastapi_modules.fastapi_leaudit.services import IOssService
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServiceImpl
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ruleServiceImpl import GetRuleServiceSingleton
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.ruleTenantScope import normalize_scoped_tenant_code, pick_effective_scoped_row
|
||||
from fastapi_modules.fastapi_leaudit.services.ruleConfigService import IRuleConfigService
|
||||
|
||||
|
||||
@@ -29,7 +30,7 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
"""规则配置页聚合服务实现。"""
|
||||
|
||||
_GLOBAL_YAML_SUMMARY_CACHE: dict[int, tuple[float, dict[str, Any]]] = {}
|
||||
_GLOBAL_PACK_SUMMARY_CACHE: tuple[float, list[RuleConfigPackListVO]] | None = None
|
||||
_GLOBAL_PACK_SUMMARY_CACHE: dict[str, tuple[float, list[RuleConfigPackListVO]]] = {}
|
||||
_GLOBAL_WARM_LOCK: asyncio.Lock | None = None
|
||||
|
||||
def __init__(self, OssService: IOssService | None = None) -> None:
|
||||
@@ -40,8 +41,11 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
self._pack_summary_cache = self.__class__._GLOBAL_PACK_SUMMARY_CACHE
|
||||
|
||||
@classmethod
|
||||
def _set_pack_summary_cache(cls, value: tuple[float, list[RuleConfigPackListVO]] | None) -> None:
|
||||
cls._GLOBAL_PACK_SUMMARY_CACHE = value
|
||||
def _set_pack_summary_cache(cls, cache_key: str, value: tuple[float, list[RuleConfigPackListVO]] | None) -> None:
|
||||
if value is None:
|
||||
cls._GLOBAL_PACK_SUMMARY_CACHE.pop(cache_key, None)
|
||||
return
|
||||
cls._GLOBAL_PACK_SUMMARY_CACHE[cache_key] = value
|
||||
|
||||
@classmethod
|
||||
def _get_warm_lock(cls) -> asyncio.Lock:
|
||||
@@ -51,29 +55,30 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
|
||||
def InvalidateSummaryCaches(self, version_ids: list[int] | None = None) -> None:
|
||||
"""清理规则摘要缓存;version_ids 为空时清空全部。"""
|
||||
self.__class__._set_pack_summary_cache(None)
|
||||
self.__class__._GLOBAL_PACK_SUMMARY_CACHE.clear()
|
||||
if version_ids is None:
|
||||
self._yaml_summary_cache.clear()
|
||||
return
|
||||
for version_id in {int(item) for item in version_ids if item is not None}:
|
||||
self._yaml_summary_cache.pop(version_id, None)
|
||||
|
||||
async def WarmPackSummaries(self, force: bool = False) -> list[RuleConfigPackListVO]:
|
||||
async def WarmPackSummaries(self, force: bool = False, CurrentUserId: int | None = None) -> list[RuleConfigPackListVO]:
|
||||
"""预热规则列表摘要缓存。"""
|
||||
async with self.__class__._get_warm_lock():
|
||||
if force:
|
||||
self.InvalidateSummaryCaches()
|
||||
return await self.ListPackSummaries()
|
||||
return await self.ListPackSummaries(CurrentUserId=CurrentUserId)
|
||||
|
||||
async def ListPacks(self) -> list[RuleConfigPackVO]:
|
||||
async def ListPacks(self, CurrentUserId: int | None = None) -> list[RuleConfigPackVO]:
|
||||
"""列出规则配置页所需的全部 pack。"""
|
||||
rows = await self._load_pack_rows()
|
||||
rule_set_map = await self._load_rule_set_meta_map()
|
||||
return [await self._build_pack_vo(row, rule_set_map) for row in rows]
|
||||
rule_set_map = await self._load_rule_set_meta_map(CurrentUserId=CurrentUserId)
|
||||
return [await self._build_pack_vo(row, rule_set_map, CurrentUserId=CurrentUserId) for row in rows]
|
||||
|
||||
async def ListPackSummaries(self) -> list[RuleConfigPackListVO]:
|
||||
async def ListPackSummaries(self, CurrentUserId: int | None = None) -> list[RuleConfigPackListVO]:
|
||||
"""列出规则列表页所需的轻量 pack。"""
|
||||
cached = self.__class__._GLOBAL_PACK_SUMMARY_CACHE
|
||||
cache_key = self._summary_cache_key(CurrentUserId)
|
||||
cached = self.__class__._GLOBAL_PACK_SUMMARY_CACHE.get(cache_key)
|
||||
now = time.monotonic()
|
||||
if cached and now - cached[0] <= 60:
|
||||
return cached[1]
|
||||
@@ -82,10 +87,11 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
if not rows:
|
||||
return []
|
||||
|
||||
current_user = await self._load_current_user(CurrentUserId)
|
||||
group_ids = [int(row["group_id"]) for row in rows]
|
||||
binding_map = await self._load_effective_binding_map(group_ids)
|
||||
binding_map = await self._load_effective_binding_map(group_ids, CurrentUserId=CurrentUserId)
|
||||
rule_set_ids = sorted({int(item["rule_set_id"]) for item in binding_map.values() if item.get("rule_set_id") is not None})
|
||||
rule_set_map = await self._load_rule_set_meta_map_by_ids(rule_set_ids)
|
||||
rule_set_map = await self._load_rule_set_meta_map_by_ids(rule_set_ids, CurrentUserId=CurrentUserId)
|
||||
latest_version_map = await self._load_latest_version_map(rule_set_ids)
|
||||
|
||||
base_items: list[dict[str, Any]] = []
|
||||
@@ -93,53 +99,16 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
for row in rows:
|
||||
group_id = int(row["group_id"])
|
||||
binding = binding_map.get(group_id)
|
||||
document_type = str(row["document_type_name"] or "").strip()
|
||||
main_type = str(row["main_type"] or "").strip()
|
||||
subtype = str(row["subtype"] or "").strip() or "通用"
|
||||
module_type = str(row["entry_module_name"] or "").strip() or (f"{document_type}评查" if document_type else "规则配置")
|
||||
|
||||
binding_id: int | None = None
|
||||
rule_set_id: int | None = None
|
||||
rule_type: str | None = None
|
||||
rule_name: str | None = None
|
||||
current_version_id: int | None = None
|
||||
fallback_version_id: int | None = None
|
||||
resolved_version_id: int | None = None
|
||||
has_usable_version = False
|
||||
usable_rule_count = 0
|
||||
|
||||
if binding:
|
||||
binding_id = int(binding["id"])
|
||||
rule_set_id = int(binding["rule_set_id"])
|
||||
rule_set_meta = rule_set_map.get(rule_set_id, {})
|
||||
rule_type = str(rule_set_meta.get("rule_type") or "") or None
|
||||
rule_name = str(rule_set_meta.get("rule_name") or "") or None
|
||||
current_version_id = self._to_int(rule_set_meta.get("current_version_id"))
|
||||
fallback_version_id = self._to_int(rule_set_meta.get("fallback_version_id"))
|
||||
has_usable_version = bool(rule_set_meta.get("has_usable_version"))
|
||||
resolved_version_id = current_version_id or fallback_version_id or latest_version_map.get(rule_set_id)
|
||||
if resolved_version_id is not None:
|
||||
resolved_version_ids.add(resolved_version_id)
|
||||
|
||||
base_items.append({
|
||||
"packId": group_id,
|
||||
"groupId": group_id,
|
||||
"rootGroupId": self._to_int(row.get("root_group_id")),
|
||||
"bindingId": binding_id,
|
||||
"ruleSetId": rule_set_id,
|
||||
"ruleType": rule_type,
|
||||
"ruleName": rule_name,
|
||||
"currentVersionId": current_version_id,
|
||||
"fallbackVersionId": fallback_version_id,
|
||||
"resolvedVersionId": resolved_version_id,
|
||||
"hasUsableVersion": has_usable_version,
|
||||
"usableRuleCount": usable_rule_count,
|
||||
"documentTypeId": self._to_int(row.get("document_type_id")),
|
||||
"documentType": document_type,
|
||||
"moduleType": module_type,
|
||||
"mainType": main_type or document_type,
|
||||
"subtype": subtype,
|
||||
})
|
||||
item = self._build_pack_summary_item(
|
||||
row=row,
|
||||
binding=binding,
|
||||
rule_set_map=rule_set_map,
|
||||
latest_version_map=latest_version_map,
|
||||
current_user=current_user,
|
||||
)
|
||||
if item.get("resolvedVersionId") is not None:
|
||||
resolved_version_ids.add(int(item["resolvedVersionId"]))
|
||||
base_items.append(item)
|
||||
|
||||
version_oss_map = await self._load_version_oss_map(sorted(resolved_version_ids))
|
||||
yaml_summary_map = await self._load_yaml_summaries(version_oss_map)
|
||||
@@ -160,11 +129,11 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
packs.append(RuleConfigPackListVO(**item))
|
||||
|
||||
cache_value = (time.monotonic(), packs)
|
||||
self.__class__._set_pack_summary_cache(cache_value)
|
||||
self.__class__._set_pack_summary_cache(cache_key, cache_value)
|
||||
self._pack_summary_cache = cache_value
|
||||
return packs
|
||||
|
||||
async def GetPack(self, PackId: int) -> RuleConfigPackVO:
|
||||
async def GetPack(self, PackId: int, CurrentUserId: int | None = None) -> RuleConfigPackVO:
|
||||
"""获取单个规则配置 pack。"""
|
||||
async with GetAsyncSession() as session:
|
||||
row = (
|
||||
@@ -200,13 +169,20 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
if not row:
|
||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "规则配置 pack 不存在")
|
||||
|
||||
rule_set_map = await self._load_rule_set_meta_map()
|
||||
return await self._build_pack_vo(row, rule_set_map)
|
||||
rule_set_map = await self._load_rule_set_meta_map(CurrentUserId=CurrentUserId)
|
||||
return await self._build_pack_vo(row, rule_set_map, CurrentUserId=CurrentUserId)
|
||||
|
||||
async def _build_pack_vo(self, row, rule_set_map: dict[int, dict[str, object]]) -> RuleConfigPackVO:
|
||||
async def _build_pack_vo(
|
||||
self,
|
||||
row,
|
||||
rule_set_map: dict[int, dict[str, object]],
|
||||
*,
|
||||
CurrentUserId: int | None = None,
|
||||
) -> RuleConfigPackVO:
|
||||
"""构建单个 pack 聚合对象。"""
|
||||
group_id = int(row["group_id"])
|
||||
binding = await self._load_effective_binding(group_id)
|
||||
binding = await self._load_effective_binding(group_id, CurrentUserId=CurrentUserId)
|
||||
current_user = await self._load_current_user(CurrentUserId)
|
||||
|
||||
document_type = str(row["document_type_name"] or "").strip()
|
||||
main_type = str(row["main_type"] or "").strip()
|
||||
@@ -224,23 +200,33 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
has_usable_version = False
|
||||
usable_rule_count = 0
|
||||
binding_id: int | None = None
|
||||
rules: list[RuleConfigPackRuleSummaryVO] = []
|
||||
source_fields = self._resolve_source_fields(binding=binding, current_user=current_user)
|
||||
|
||||
if binding:
|
||||
binding_id = int(binding["id"])
|
||||
rule_set_id = int(binding["rule_set_id"])
|
||||
rule_set_meta = rule_set_map.get(rule_set_id, {})
|
||||
rule_type = str(rule_set_meta.get("rule_type") or "") or None
|
||||
rule_name = str(rule_set_meta.get("rule_name") or "") or None
|
||||
current_version_id = self._to_int(rule_set_meta.get("current_version_id"))
|
||||
fallback_version_id = self._to_int(rule_set_meta.get("fallback_version_id"))
|
||||
resolved_version_id = current_version_id or fallback_version_id
|
||||
has_usable_version = bool(rule_set_meta.get("has_usable_version"))
|
||||
usable_rule_count = int(rule_set_meta.get("usable_rule_count") or 0)
|
||||
if resolved_version_id is None:
|
||||
resolved_version_id = await self._load_latest_version_id(rule_set_id)
|
||||
if resolved_version_id is not None:
|
||||
yaml_text = await self._load_yaml_text_by_version_id(resolved_version_id)
|
||||
source_status = "ready" if yaml_text.strip() else "missing"
|
||||
bound_rule_set_id = int(binding["rule_set_id"])
|
||||
rule_set_meta = rule_set_map.get(bound_rule_set_id)
|
||||
if rule_set_meta:
|
||||
rule_set_id = bound_rule_set_id
|
||||
rule_type = str(rule_set_meta.get("rule_type") or "") or None
|
||||
rule_name = str(rule_set_meta.get("rule_name") or "") or None
|
||||
current_version_id = self._to_int(rule_set_meta.get("current_version_id"))
|
||||
fallback_version_id = self._to_int(rule_set_meta.get("fallback_version_id"))
|
||||
resolved_version_id = current_version_id or fallback_version_id
|
||||
has_usable_version = bool(rule_set_meta.get("has_usable_version"))
|
||||
usable_rule_count = int(rule_set_meta.get("usable_rule_count") or 0)
|
||||
if resolved_version_id is None:
|
||||
resolved_version_id = await self._load_latest_version_id(rule_set_id)
|
||||
if resolved_version_id is not None:
|
||||
yaml_text = await self._load_yaml_text_by_version_id(resolved_version_id)
|
||||
source_status = "ready" if yaml_text.strip() else "missing"
|
||||
version_oss_map = await self._load_version_oss_map([resolved_version_id])
|
||||
summary = (await self._load_yaml_summaries(version_oss_map)).get(resolved_version_id)
|
||||
if summary and summary.get("loaded"):
|
||||
summary_rules = list(summary.get("rules") or [])
|
||||
rules = [RuleConfigPackRuleSummaryVO(**rule) for rule in summary_rules]
|
||||
usable_rule_count = len(rules)
|
||||
|
||||
return RuleConfigPackVO(
|
||||
packId=group_id,
|
||||
@@ -248,6 +234,10 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
rootGroupId=self._to_int(row.get("root_group_id")),
|
||||
bindingId=binding_id,
|
||||
ruleSetId=rule_set_id,
|
||||
effectiveTenantCode=source_fields["effectiveTenantCode"],
|
||||
effectiveScopeType=source_fields["effectiveScopeType"],
|
||||
isInherited=source_fields["isInherited"],
|
||||
sourceRuleSetId=self._to_int(rule_set_map.get(rule_set_id, {}).get("source_rule_set_id")) if rule_set_id is not None else None,
|
||||
ruleType=rule_type,
|
||||
ruleName=rule_name,
|
||||
currentVersionId=current_version_id,
|
||||
@@ -262,6 +252,7 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
subtype=subtype,
|
||||
yamlText=yaml_text,
|
||||
sourceStatus=source_status,
|
||||
rules=rules,
|
||||
)
|
||||
|
||||
async def _load_pack_rows(self):
|
||||
@@ -294,57 +285,61 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
)
|
||||
).mappings().all()
|
||||
|
||||
async def _load_effective_binding(self, group_id: int):
|
||||
async def _load_effective_binding(self, group_id: int, CurrentUserId: int | None = None):
|
||||
"""读取当前二级分组实际生效的规则集绑定。"""
|
||||
async with GetAsyncSession() as session:
|
||||
row = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, rule_set_id
|
||||
FROM leaudit_rule_group_bindings
|
||||
WHERE group_id = :group_id
|
||||
AND deleted_at IS NULL
|
||||
AND is_active = TRUE
|
||||
ORDER BY priority DESC, id ASC
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"group_id": group_id},
|
||||
)
|
||||
).mappings().first()
|
||||
return row
|
||||
return (await self._load_effective_binding_map([group_id], CurrentUserId=CurrentUserId)).get(group_id)
|
||||
|
||||
async def _load_effective_binding_map(self, group_ids: list[int]) -> dict[int, dict[str, Any]]:
|
||||
async def _load_effective_binding_map(
|
||||
self,
|
||||
group_ids: list[int],
|
||||
CurrentUserId: int | None = None,
|
||||
) -> dict[int, dict[str, Any]]:
|
||||
if not group_ids:
|
||||
return {}
|
||||
async with GetAsyncSession() as session:
|
||||
current_user = await self.RuleService._get_current_user_context(session, CurrentUserId)
|
||||
binding_tenant_expr = (
|
||||
"COALESCE(NULLIF(BTRIM(tenant_code), ''), 'PROVINCIAL')"
|
||||
if await self.RuleService._column_exists(session, "leaudit_rule_group_bindings", "tenant_code")
|
||||
else "'PROVINCIAL'"
|
||||
)
|
||||
rows = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT id, group_id, rule_set_id
|
||||
FROM (
|
||||
SELECT
|
||||
id,
|
||||
group_id,
|
||||
rule_set_id,
|
||||
ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY priority DESC, id ASC) AS rn
|
||||
FROM leaudit_rule_group_bindings
|
||||
WHERE deleted_at IS NULL
|
||||
AND is_active = TRUE
|
||||
AND group_id = ANY(:group_ids)
|
||||
) t
|
||||
WHERE rn = 1
|
||||
f"""
|
||||
SELECT
|
||||
id,
|
||||
group_id,
|
||||
rule_set_id,
|
||||
priority,
|
||||
{binding_tenant_expr} AS tenant_code
|
||||
FROM leaudit_rule_group_bindings
|
||||
WHERE deleted_at IS NULL
|
||||
AND is_active = TRUE
|
||||
AND group_id = ANY(:group_ids)
|
||||
ORDER BY group_id ASC, priority DESC, id ASC
|
||||
"""
|
||||
),
|
||||
{"group_ids": group_ids},
|
||||
)
|
||||
).mappings().all()
|
||||
return {int(row["group_id"]): dict(row) for row in rows}
|
||||
grouped: dict[int, list[dict[str, Any]]] = defaultdict(list)
|
||||
for row in rows:
|
||||
grouped[int(row["group_id"])].append(dict(row))
|
||||
binding_map: dict[int, dict[str, Any]] = {}
|
||||
tenant_code = str(current_user.get("tenant_code") or "") or None if current_user else None
|
||||
for group_id, group_rows in grouped.items():
|
||||
effective = pick_effective_scoped_row(group_rows, tenant_code)
|
||||
if effective is not None:
|
||||
binding_map[group_id] = dict(effective)
|
||||
return binding_map
|
||||
|
||||
async def _load_rule_set_meta_map(self) -> dict[int, dict[str, object]]:
|
||||
items = await self.RuleService.ListSets()
|
||||
async def _load_current_user(self, CurrentUserId: int | None = None) -> dict[str, object] | None:
|
||||
async with GetAsyncSession() as session:
|
||||
return await self.RuleService._get_current_user_context(session, CurrentUserId)
|
||||
|
||||
async def _load_rule_set_meta_map(self, CurrentUserId: int | None = None) -> dict[int, dict[str, object]]:
|
||||
items = await self.RuleService.ListSets(CurrentUserId=CurrentUserId)
|
||||
return {
|
||||
item.id: {
|
||||
"rule_type": item.ruleType,
|
||||
@@ -353,60 +348,130 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
"fallback_version_id": item.fallbackVersionId,
|
||||
"has_usable_version": item.hasUsableVersion,
|
||||
"usable_rule_count": item.usableRuleCount,
|
||||
"source_rule_set_id": getattr(item, "sourceRuleSetId", None),
|
||||
}
|
||||
for item in items
|
||||
}
|
||||
|
||||
async def _load_rule_set_meta_map_by_ids(self, rule_set_ids: list[int]) -> dict[int, dict[str, object]]:
|
||||
async def _load_rule_set_meta_map_by_ids(
|
||||
self,
|
||||
rule_set_ids: list[int],
|
||||
CurrentUserId: int | None = None,
|
||||
) -> dict[int, dict[str, object]]:
|
||||
if not rule_set_ids:
|
||||
return {}
|
||||
async with GetAsyncSession() as session:
|
||||
rows = (
|
||||
await session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
rs.id,
|
||||
rs.rule_type,
|
||||
rs.rule_name,
|
||||
rs.current_version_id,
|
||||
current_rv.id AS usable_current_version_id,
|
||||
fallback_rv.id AS fallback_version_id,
|
||||
CASE
|
||||
WHEN current_rv.id IS NOT NULL OR fallback_rv.id IS NOT NULL THEN TRUE
|
||||
ELSE FALSE
|
||||
END AS has_usable_version
|
||||
FROM leaudit_rule_sets rs
|
||||
LEFT JOIN leaudit_rule_versions current_rv
|
||||
ON current_rv.id = rs.current_version_id
|
||||
AND current_rv.status = 'published'
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT rv.id
|
||||
FROM leaudit_rule_versions rv
|
||||
WHERE rv.rule_set_id = rs.id
|
||||
AND rv.status = 'published'
|
||||
AND (rs.current_version_id IS NULL OR rv.id <> rs.current_version_id)
|
||||
ORDER BY rv.version_seq DESC, rv.id DESC
|
||||
LIMIT 1
|
||||
) fallback_rv ON TRUE
|
||||
WHERE rs.deleted_at IS NULL
|
||||
AND rs.id = ANY(:rule_set_ids)
|
||||
"""
|
||||
),
|
||||
{"rule_set_ids": rule_set_ids},
|
||||
)
|
||||
).mappings().all()
|
||||
items = await self.RuleService.ListSets(CurrentUserId=CurrentUserId)
|
||||
return {
|
||||
int(row["id"]): {
|
||||
"rule_type": row["rule_type"],
|
||||
"rule_name": row["rule_name"],
|
||||
"current_version_id": row["current_version_id"],
|
||||
"fallback_version_id": row["fallback_version_id"],
|
||||
"has_usable_version": row["has_usable_version"],
|
||||
item.id: {
|
||||
"rule_type": item.ruleType,
|
||||
"rule_name": item.ruleName,
|
||||
"current_version_id": item.currentVersionId,
|
||||
"fallback_version_id": item.fallbackVersionId,
|
||||
"has_usable_version": item.hasUsableVersion,
|
||||
"usable_rule_count": item.usableRuleCount,
|
||||
"source_rule_set_id": getattr(item, "sourceRuleSetId", None),
|
||||
}
|
||||
for row in rows
|
||||
for item in items
|
||||
if item.id in set(rule_set_ids)
|
||||
}
|
||||
|
||||
def _build_pack_summary_item(
|
||||
self,
|
||||
*,
|
||||
row,
|
||||
binding: dict[str, Any] | None,
|
||||
rule_set_map: dict[int, dict[str, object]],
|
||||
latest_version_map: dict[int, int],
|
||||
current_user: dict[str, object] | None,
|
||||
) -> dict[str, Any]:
|
||||
document_type = str(row["document_type_name"] or "").strip()
|
||||
main_type = str(row["main_type"] or "").strip()
|
||||
subtype = str(row["subtype"] or "").strip() or "通用"
|
||||
module_type = str(row["entry_module_name"] or "").strip() or (f"{document_type}评查" if document_type else "规则配置")
|
||||
|
||||
binding_id: int | None = None
|
||||
rule_set_id: int | None = None
|
||||
rule_type: str | None = None
|
||||
rule_name: str | None = None
|
||||
current_version_id: int | None = None
|
||||
fallback_version_id: int | None = None
|
||||
resolved_version_id: int | None = None
|
||||
has_usable_version = False
|
||||
usable_rule_count = 0
|
||||
source_rule_set_id: int | None = None
|
||||
source_fields = self._resolve_source_fields(binding=binding, current_user=current_user)
|
||||
|
||||
if binding:
|
||||
binding_id = int(binding["id"])
|
||||
bound_rule_set_id = int(binding["rule_set_id"])
|
||||
rule_set_meta = rule_set_map.get(bound_rule_set_id)
|
||||
if rule_set_meta:
|
||||
rule_set_id = bound_rule_set_id
|
||||
rule_type = str(rule_set_meta.get("rule_type") or "") or None
|
||||
rule_name = str(rule_set_meta.get("rule_name") or "") or None
|
||||
current_version_id = self._to_int(rule_set_meta.get("current_version_id"))
|
||||
fallback_version_id = self._to_int(rule_set_meta.get("fallback_version_id"))
|
||||
has_usable_version = bool(rule_set_meta.get("has_usable_version"))
|
||||
source_rule_set_id = self._to_int(rule_set_meta.get("source_rule_set_id"))
|
||||
resolved_version_id = current_version_id or fallback_version_id or latest_version_map.get(rule_set_id)
|
||||
|
||||
return {
|
||||
"packId": int(row["group_id"]),
|
||||
"groupId": int(row["group_id"]),
|
||||
"rootGroupId": self._to_int(row.get("root_group_id")),
|
||||
"bindingId": binding_id,
|
||||
"ruleSetId": rule_set_id,
|
||||
"effectiveTenantCode": source_fields["effectiveTenantCode"],
|
||||
"effectiveScopeType": source_fields["effectiveScopeType"],
|
||||
"isInherited": source_fields["isInherited"],
|
||||
"sourceRuleSetId": source_rule_set_id,
|
||||
"ruleType": rule_type,
|
||||
"ruleName": rule_name,
|
||||
"currentVersionId": current_version_id,
|
||||
"fallbackVersionId": fallback_version_id,
|
||||
"resolvedVersionId": resolved_version_id,
|
||||
"hasUsableVersion": has_usable_version,
|
||||
"usableRuleCount": usable_rule_count,
|
||||
"documentTypeId": self._to_int(row.get("document_type_id")),
|
||||
"documentType": document_type,
|
||||
"moduleType": module_type,
|
||||
"mainType": main_type or document_type,
|
||||
"subtype": subtype,
|
||||
}
|
||||
|
||||
def _resolve_source_fields(
|
||||
self,
|
||||
*,
|
||||
binding: dict[str, Any] | None,
|
||||
current_user: dict[str, object] | None,
|
||||
) -> dict[str, Any]:
|
||||
if not binding:
|
||||
return {
|
||||
"effectiveTenantCode": None,
|
||||
"effectiveScopeType": None,
|
||||
"isInherited": False,
|
||||
}
|
||||
|
||||
effective_tenant_code = normalize_scoped_tenant_code(str(binding.get("tenant_code") or ""))
|
||||
effective_scope_type = self._scope_type_from_tenant_code(effective_tenant_code)
|
||||
is_tenant_user = bool(current_user) and not bool(current_user.get("is_global")) and bool(str(current_user.get("tenant_code") or "").strip())
|
||||
is_inherited = is_tenant_user and effective_scope_type in {"PROVINCIAL", "PUBLIC"}
|
||||
return {
|
||||
"effectiveTenantCode": effective_tenant_code,
|
||||
"effectiveScopeType": effective_scope_type,
|
||||
"isInherited": is_inherited,
|
||||
}
|
||||
|
||||
def _scope_type_from_tenant_code(self, tenant_code: str | None) -> str | None:
|
||||
normalized = normalize_scoped_tenant_code(tenant_code) if tenant_code is not None else None
|
||||
if not normalized:
|
||||
return None
|
||||
if normalized == "PUBLIC":
|
||||
return "PUBLIC"
|
||||
if normalized == "PROVINCIAL":
|
||||
return "PROVINCIAL"
|
||||
return "TENANT"
|
||||
|
||||
async def _load_version_oss_map(self, version_ids: list[int]) -> dict[int, str]:
|
||||
if not version_ids:
|
||||
return {}
|
||||
@@ -644,6 +709,10 @@ class RuleConfigServiceImpl(IRuleConfigService):
|
||||
return None
|
||||
return int(value)
|
||||
|
||||
@staticmethod
|
||||
def _summary_cache_key(CurrentUserId: int | None) -> str:
|
||||
return f"user:{CurrentUserId or 0}"
|
||||
|
||||
|
||||
_RULE_CONFIG_SERVICE_SINGLETON: RuleConfigServiceImpl | None = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user