mandate admin fixes
This commit is contained in:
parent
bc370ef475
commit
695c652a56
7 changed files with 162 additions and 38 deletions
|
|
@ -1074,19 +1074,66 @@ class AppObjects:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _deleteUserReferencedData(self, userId: str) -> None:
|
def _deleteUserReferencedData(self, userId: str) -> None:
|
||||||
"""Deletes all data associated with a user."""
|
"""Deletes all data associated with a user (full cascade)."""
|
||||||
try:
|
try:
|
||||||
# Delete user auth events
|
from modules.datamodels.datamodelRbac import FeatureAccessRole, UserMandateRole
|
||||||
|
from modules.datamodels.datamodelNotification import UserNotification
|
||||||
|
from modules.datamodels.datamodelInvitation import Invitation
|
||||||
|
|
||||||
|
# 1. FeatureAccess + FeatureAccessRole
|
||||||
|
accesses = self.db.getRecordset(FeatureAccess, recordFilter={"userId": userId})
|
||||||
|
for acc in accesses:
|
||||||
|
accId = acc.get("id")
|
||||||
|
if not accId:
|
||||||
|
continue
|
||||||
|
roles = self.db.getRecordset(FeatureAccessRole, recordFilter={"featureAccessId": accId})
|
||||||
|
for role in roles:
|
||||||
|
self.db.recordDelete(FeatureAccessRole, role.get("id"))
|
||||||
|
self.db.recordDelete(FeatureAccess, accId)
|
||||||
|
if accesses:
|
||||||
|
logger.info(f"User cascade: deleted {len(accesses)} FeatureAccess records for user {userId}")
|
||||||
|
|
||||||
|
# 2. UserMandate + UserMandateRole
|
||||||
|
memberships = self.db.getRecordset(UserMandate, recordFilter={"userId": userId})
|
||||||
|
for um in memberships:
|
||||||
|
umId = um.get("id")
|
||||||
|
if not umId:
|
||||||
|
continue
|
||||||
|
umRoles = self.db.getRecordset(UserMandateRole, recordFilter={"userMandateId": umId})
|
||||||
|
for umr in umRoles:
|
||||||
|
self.db.recordDelete(UserMandateRole, umr.get("id"))
|
||||||
|
self.db.recordDelete(UserMandate, umId)
|
||||||
|
if memberships:
|
||||||
|
logger.info(f"User cascade: deleted {len(memberships)} UserMandate records for user {userId}")
|
||||||
|
|
||||||
|
# 3. UserNotifications
|
||||||
|
notifications = self.db.getRecordset(UserNotification, recordFilter={"userId": userId})
|
||||||
|
for notif in notifications:
|
||||||
|
self.db.recordDelete(UserNotification, notif.get("id"))
|
||||||
|
if notifications:
|
||||||
|
logger.info(f"User cascade: deleted {len(notifications)} notifications for user {userId}")
|
||||||
|
|
||||||
|
# 4. Invitations (by email)
|
||||||
|
user = self.getUser(userId)
|
||||||
|
userEmail = getattr(user, "email", None) if user else None
|
||||||
|
if userEmail:
|
||||||
|
invitations = self.db.getRecordset(Invitation, recordFilter={"email": userEmail})
|
||||||
|
for inv in invitations:
|
||||||
|
self.db.recordDelete(Invitation, inv.get("id"))
|
||||||
|
if invitations:
|
||||||
|
logger.info(f"User cascade: deleted {len(invitations)} invitations for {userEmail}")
|
||||||
|
|
||||||
|
# 5. AuthEvents
|
||||||
events = self.db.getRecordset(AuthEvent, recordFilter={"userId": userId})
|
events = self.db.getRecordset(AuthEvent, recordFilter={"userId": userId})
|
||||||
for event in events:
|
for event in events:
|
||||||
self.db.recordDelete(AuthEvent, event["id"])
|
self.db.recordDelete(AuthEvent, event["id"])
|
||||||
|
|
||||||
# Delete user tokens
|
# 6. Tokens
|
||||||
tokens = self.db.getRecordset(Token, recordFilter={"userId": userId})
|
tokens = self.db.getRecordset(Token, recordFilter={"userId": userId})
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
self.db.recordDelete(Token, token["id"])
|
self.db.recordDelete(Token, token["id"])
|
||||||
|
|
||||||
# Delete user connections
|
# 7. UserConnections
|
||||||
connections = self.db.getRecordset(
|
connections = self.db.getRecordset(
|
||||||
UserConnection, recordFilter={"userId": userId}
|
UserConnection, recordFilter={"userId": userId}
|
||||||
)
|
)
|
||||||
|
|
@ -1448,14 +1495,23 @@ class AppObjects:
|
||||||
|
|
||||||
self.createUserMandate(userId, mandateId, roleIds=[adminRoleId], skipCapacityCheck=True)
|
self.createUserMandate(userId, mandateId, roleIds=[adminRoleId], skipCapacityCheck=True)
|
||||||
|
|
||||||
|
from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
targetStatus = SubscriptionStatusEnum.TRIALING if plan.trialDays else SubscriptionStatusEnum.ACTIVE
|
||||||
subscription = MandateSubscription(
|
subscription = MandateSubscription(
|
||||||
mandateId=mandateId,
|
mandateId=mandateId,
|
||||||
planKey=planKey,
|
planKey=planKey,
|
||||||
status=SubscriptionStatusEnum.PENDING,
|
status=targetStatus,
|
||||||
|
startedAt=now.isoformat(),
|
||||||
|
currentPeriodStart=now.isoformat(),
|
||||||
)
|
)
|
||||||
if plan.trialDays:
|
if plan.trialDays:
|
||||||
pass # trialEndsAt set on ACTIVE/TRIALING transition
|
trialEnd = now + timedelta(days=plan.trialDays)
|
||||||
from modules.interfaces.interfaceDbSubscription import _getRootInterface as _getSubRoot
|
subscription.trialEndsAt = trialEnd.isoformat()
|
||||||
|
subscription.currentPeriodEnd = trialEnd.isoformat()
|
||||||
|
|
||||||
subInterface = _getSubRoot()
|
subInterface = _getSubRoot()
|
||||||
subInterface.createSubscription(subscription)
|
subInterface.createSubscription(subscription)
|
||||||
|
|
||||||
|
|
@ -1584,8 +1640,9 @@ class AppObjects:
|
||||||
if not mandate:
|
if not mandate:
|
||||||
raise ValueError(f"Mandate {mandateId} not found")
|
raise ValueError(f"Mandate {mandateId} not found")
|
||||||
|
|
||||||
# Strip immutable/protected fields from update data
|
_protectedFields = {"id"}
|
||||||
_protectedFields = {"id", "isSystem"}
|
if not getattr(self.currentUser, "isSysAdmin", False):
|
||||||
|
_protectedFields.add("isSystem")
|
||||||
_sanitizedData = {k: v for k, v in updateData.items() if k not in _protectedFields}
|
_sanitizedData = {k: v for k, v in updateData.items() if k not in _protectedFields}
|
||||||
|
|
||||||
# Update mandate data using model
|
# Update mandate data using model
|
||||||
|
|
@ -1761,6 +1818,14 @@ class AppObjects:
|
||||||
if billingAccounts or billingSettings:
|
if billingAccounts or billingSettings:
|
||||||
logger.info(f"Cascade: deleted billing data for mandate {mandateId}")
|
logger.info(f"Cascade: deleted billing data for mandate {mandateId}")
|
||||||
|
|
||||||
|
# 3c. Delete Invitations for this mandate
|
||||||
|
from modules.datamodels.datamodelInvitation import Invitation
|
||||||
|
invitations = self.db.getRecordset(Invitation, recordFilter={"mandateId": mandateId})
|
||||||
|
for inv in invitations:
|
||||||
|
self.db.recordDelete(Invitation, inv.get("id"))
|
||||||
|
if invitations:
|
||||||
|
logger.info(f"Cascade: deleted {len(invitations)} Invitations for mandate {mandateId}")
|
||||||
|
|
||||||
# 4. Delete mandate-level Roles
|
# 4. Delete mandate-level Roles
|
||||||
from modules.datamodels.datamodelRbac import Role, AccessRule
|
from modules.datamodels.datamodelRbac import Role, AccessRule
|
||||||
roles = self.db.getRecordset(Role, recordFilter={"mandateId": mandateId})
|
roles = self.db.getRecordset(Role, recordFilter={"mandateId": mandateId})
|
||||||
|
|
|
||||||
|
|
@ -1145,7 +1145,7 @@ class BillingObjects:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
mandate = rootInterface.getMandate(mandateId)
|
mandate = rootInterface.getMandate(mandateId)
|
||||||
if not mandate:
|
if not mandate or not getattr(mandate, "enabled", True):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
mandateName = getattr(mandate, 'label', None) or getattr(mandate, 'name', None) or (mandate.get("label") or mandate.get("name", "") if isinstance(mandate, dict) else "")
|
mandateName = getattr(mandate, 'label', None) or getattr(mandate, 'name', None) or (mandate.get("label") or mandate.get("name", "") if isinstance(mandate, dict) else "")
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,8 @@ def get_my_feature_instances(
|
||||||
mandateId = str(instance.mandateId)
|
mandateId = str(instance.mandateId)
|
||||||
if mandateId not in mandatesMap:
|
if mandateId not in mandatesMap:
|
||||||
mandate = rootInterface.getMandate(mandateId)
|
mandate = rootInterface.getMandate(mandateId)
|
||||||
|
if mandate and not getattr(mandate, "enabled", True):
|
||||||
|
continue
|
||||||
if mandate:
|
if mandate:
|
||||||
mandatesMap[mandateId] = {
|
mandatesMap[mandateId] = {
|
||||||
"id": mandateId,
|
"id": mandateId,
|
||||||
|
|
|
||||||
|
|
@ -125,11 +125,11 @@ def get_mandates(
|
||||||
# SysAdmin: all mandates
|
# SysAdmin: all mandates
|
||||||
result = appInterface.getAllMandates(pagination=paginationParams)
|
result = appInterface.getAllMandates(pagination=paginationParams)
|
||||||
else:
|
else:
|
||||||
# MandateAdmin: only their mandates
|
# MandateAdmin: only their enabled mandates
|
||||||
allMandates = []
|
allMandates = []
|
||||||
for mandateId in adminMandateIds:
|
for mandateId in adminMandateIds:
|
||||||
mandate = appInterface.getMandate(mandateId)
|
mandate = appInterface.getMandate(mandateId)
|
||||||
if mandate:
|
if mandate and getattr(mandate, "enabled", True):
|
||||||
mandateDict = mandate if isinstance(mandate, dict) else mandate.model_dump() if hasattr(mandate, 'model_dump') else vars(mandate)
|
mandateDict = mandate if isinstance(mandate, dict) else mandate.model_dump() if hasattr(mandate, 'model_dump') else vars(mandate)
|
||||||
allMandates.append(mandateDict)
|
allMandates.append(mandateDict)
|
||||||
result = allMandates
|
result = allMandates
|
||||||
|
|
@ -411,41 +411,47 @@ def update_mandate(
|
||||||
def delete_mandate(
|
def delete_mandate(
|
||||||
request: Request,
|
request: Request,
|
||||||
mandateId: str = Path(..., description="ID of the mandate to delete"),
|
mandateId: str = Path(..., description="ID of the mandate to delete"),
|
||||||
|
force: bool = Query(False, description="Hard-delete with full cascade (irreversible)"),
|
||||||
currentUser: User = Depends(requireSysAdminRole)
|
currentUser: User = Depends(requireSysAdminRole)
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Delete a mandate.
|
Delete a mandate.
|
||||||
|
Default: soft-delete (sets enabled=False, 30-day retention).
|
||||||
|
With ?force=true: hard-delete with full cascade (irreversible).
|
||||||
|
Requires X-Confirm-Name header matching the mandate name for hard-delete.
|
||||||
MULTI-TENANT: SysAdmin-only.
|
MULTI-TENANT: SysAdmin-only.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
appInterface = interfaceDbApp.getRootInterface()
|
appInterface = interfaceDbApp.getRootInterface()
|
||||||
|
|
||||||
# Check if mandate exists
|
|
||||||
existingMandate = appInterface.getMandate(mandateId)
|
existingMandate = appInterface.getMandate(mandateId)
|
||||||
if not existingMandate:
|
if not existingMandate:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail=f"Mandate {mandateId} not found"
|
detail=f"Mandate {mandateId} not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
# MULTI-TENANT: Delete all UserMandate entries for this mandate first
|
if force:
|
||||||
userMandates = appInterface.getUserMandatesByMandate(mandateId)
|
confirmName = request.headers.get("X-Confirm-Name", "")
|
||||||
for um in userMandates:
|
mandateName = getattr(existingMandate, "name", "") or ""
|
||||||
appInterface.deleteUserMandate(str(um.userId), mandateId)
|
if confirmName != mandateName:
|
||||||
logger.info(f"Deleted {len(userMandates)} UserMandate entries for mandate {mandateId}")
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
# Delete mandate
|
detail="Hard-delete requires X-Confirm-Name header matching the mandate name"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
appInterface.deleteMandate(mandateId)
|
appInterface.deleteMandate(mandateId, force=force)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=str(e)
|
detail=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Mandate {mandateId} deleted by SysAdmin {currentUser.id}")
|
mode = "hard-deleted" if force else "soft-deleted"
|
||||||
|
logger.info(f"Mandate {mandateId} {mode} by SysAdmin {currentUser.id}")
|
||||||
return {"message": f"Mandate {mandateId} deleted successfully"}
|
|
||||||
|
return {"message": f"Mandate {mandateId} {mode} successfully"}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -678,8 +678,15 @@ def validate_invitation(
|
||||||
roleLabels = []
|
roleLabels = []
|
||||||
targetUsername = invitation.targetUsername
|
targetUsername = invitation.targetUsername
|
||||||
|
|
||||||
# Get mandate name
|
|
||||||
mandate = rootInterface.getMandate(str(mandateId)) if mandateId else None
|
mandate = rootInterface.getMandate(str(mandateId)) if mandateId else None
|
||||||
|
if mandate and not getattr(mandate, "enabled", True):
|
||||||
|
return InvitationValidation(
|
||||||
|
valid=False,
|
||||||
|
reason="Mandate is disabled",
|
||||||
|
mandateId=None,
|
||||||
|
featureInstanceId=None,
|
||||||
|
roleIds=[]
|
||||||
|
)
|
||||||
if mandate:
|
if mandate:
|
||||||
mandateName = mandate.label or mandate.name
|
mandateName = mandate.label or mandate.name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,35 @@ def _isUserAdminInMandate(db, userId: str, mandateId: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _autoActivatePending(subInterface, pendingSub: Dict[str, Any]) -> None:
|
||||||
|
"""Auto-activate a PENDING subscription to its target operative status."""
|
||||||
|
from modules.datamodels.datamodelSubscription import SubscriptionStatusEnum, BUILTIN_PLANS
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
subId = pendingSub.get("id")
|
||||||
|
planKey = pendingSub.get("planKey", "")
|
||||||
|
plan = BUILTIN_PLANS.get(planKey)
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
targetStatus = SubscriptionStatusEnum.TRIALING if plan and plan.trialDays else SubscriptionStatusEnum.ACTIVE
|
||||||
|
|
||||||
|
additionalData = {"currentPeriodStart": now.isoformat()}
|
||||||
|
if plan and plan.trialDays:
|
||||||
|
trialEnd = now + timedelta(days=plan.trialDays)
|
||||||
|
additionalData["trialEndsAt"] = trialEnd.isoformat()
|
||||||
|
additionalData["currentPeriodEnd"] = trialEnd.isoformat()
|
||||||
|
|
||||||
|
try:
|
||||||
|
subInterface.transitionStatus(
|
||||||
|
subId,
|
||||||
|
expectedFromStatus=SubscriptionStatusEnum.PENDING,
|
||||||
|
toStatus=targetStatus,
|
||||||
|
additionalData=additionalData,
|
||||||
|
)
|
||||||
|
logger.info("Auto-activated PENDING subscription %s -> %s for mandate", subId, targetStatus.value)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to auto-activate PENDING subscription %s: %s", subId, e)
|
||||||
|
|
||||||
|
|
||||||
def _getUserAdminMandateIds(db, userId: str) -> List[str]:
|
def _getUserAdminMandateIds(db, userId: str) -> List[str]:
|
||||||
"""Get all mandate IDs where user is admin."""
|
"""Get all mandate IDs where user is admin."""
|
||||||
userMandates = db.getRecordset(UserMandate, recordFilter={"userId": userId, "enabled": True})
|
userMandates = db.getRecordset(UserMandate, recordFilter={"userId": userId, "enabled": True})
|
||||||
|
|
@ -150,6 +179,8 @@ def listUserMandates(
|
||||||
records = db.getRecordset(Mandate, recordFilter={"id": mid})
|
records = db.getRecordset(Mandate, recordFilter={"id": mid})
|
||||||
if records:
|
if records:
|
||||||
m = records[0]
|
m = records[0]
|
||||||
|
if not m.get("enabled", True):
|
||||||
|
continue
|
||||||
result.append({
|
result.append({
|
||||||
"id": mid,
|
"id": mid,
|
||||||
"name": m.get("name", ""),
|
"name": m.get("name", ""),
|
||||||
|
|
@ -200,13 +231,15 @@ def getSubscriptionInfo(
|
||||||
"budgetAiCHF": None,
|
"budgetAiCHF": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
sub = allSubs[0]
|
operative = subInterface.getOperativeForMandate(mandateId)
|
||||||
|
sub = operative or allSubs[0]
|
||||||
plan = BUILTIN_PLANS.get(sub.get("planKey"))
|
plan = BUILTIN_PLANS.get(sub.get("planKey"))
|
||||||
currentInstances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": mandateId})
|
currentInstances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": mandateId})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"plan": sub.get("planKey"),
|
"plan": sub.get("planKey"),
|
||||||
"status": sub.get("status"),
|
"status": sub.get("status"),
|
||||||
|
"operative": operative is not None,
|
||||||
"maxDataVolumeMB": plan.maxDataVolumeMB if plan else None,
|
"maxDataVolumeMB": plan.maxDataVolumeMB if plan else None,
|
||||||
"maxFeatureInstances": plan.maxFeatureInstances if plan else None,
|
"maxFeatureInstances": plan.maxFeatureInstances if plan else None,
|
||||||
"budgetAiCHF": plan.budgetAiCHF if plan else None,
|
"budgetAiCHF": plan.budgetAiCHF if plan else None,
|
||||||
|
|
@ -241,7 +274,7 @@ def listStoreFeatures(
|
||||||
for um in userMandates:
|
for um in userMandates:
|
||||||
mid = um.get("mandateId")
|
mid = um.get("mandateId")
|
||||||
mRecord = db.getRecordset(Mandate, recordFilter={"id": mid})
|
mRecord = db.getRecordset(Mandate, recordFilter={"id": mid})
|
||||||
if mRecord and not mRecord[0].get("isSystem"):
|
if mRecord and not mRecord[0].get("isSystem") and mRecord[0].get("enabled", True):
|
||||||
userMandateIds.append(mid)
|
userMandateIds.append(mid)
|
||||||
|
|
||||||
storeFeatures = _getStoreFeatures(catalogService)
|
storeFeatures = _getStoreFeatures(catalogService)
|
||||||
|
|
@ -302,7 +335,22 @@ def activateStoreFeature(
|
||||||
|
|
||||||
subInterface = _getSubRoot()
|
subInterface = _getSubRoot()
|
||||||
operative = subInterface.getOperativeForMandate(mandateId)
|
operative = subInterface.getOperativeForMandate(mandateId)
|
||||||
|
|
||||||
if not operative:
|
if not operative:
|
||||||
|
allSubs = subInterface.listForMandate(mandateId)
|
||||||
|
pendingSubs = [s for s in allSubs if s.get("status") == SubscriptionStatusEnum.PENDING.value]
|
||||||
|
if pendingSubs:
|
||||||
|
_autoActivatePending(subInterface, pendingSubs[0])
|
||||||
|
operative = subInterface.getOperativeForMandate(mandateId)
|
||||||
|
|
||||||
|
if not operative:
|
||||||
|
allSubs = subInterface.listForMandate(mandateId)
|
||||||
|
statuses = [s.get("status") for s in allSubs] if allSubs else []
|
||||||
|
logger.warning(
|
||||||
|
"Store activate 402: no operative subscription for mandate %s. "
|
||||||
|
"Found %d subscription(s) with statuses: %s",
|
||||||
|
mandateId, len(allSubs), statuses,
|
||||||
|
)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_402_PAYMENT_REQUIRED,
|
status_code=status.HTTP_402_PAYMENT_REQUIRED,
|
||||||
detail="Kein aktives Abonnement. Bitte zuerst ein Abo abschliessen.",
|
detail="Kein aktives Abonnement. Bitte zuerst ein Abo abschliessen.",
|
||||||
|
|
@ -310,14 +358,8 @@ def activateStoreFeature(
|
||||||
|
|
||||||
planKey = operative.get("planKey", "")
|
planKey = operative.get("planKey", "")
|
||||||
plan = BUILTIN_PLANS.get(planKey)
|
plan = BUILTIN_PLANS.get(planKey)
|
||||||
isBillable = plan is not None and (plan.pricePerFeatureInstanceCHF or 0) > 0
|
hasStripeIds = bool(operative.get("stripeSubscriptionId") and operative.get("stripeItemIdInstances"))
|
||||||
|
isBillable = hasStripeIds and plan is not None and (plan.pricePerFeatureInstanceCHF or 0) > 0
|
||||||
if isBillable:
|
|
||||||
if not operative.get("stripeSubscriptionId") or not operative.get("stripeItemIdInstances"):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_402_PAYMENT_REQUIRED,
|
|
||||||
detail="Stripe-Abonnement ist nicht vollständig eingerichtet — Aktivierung nicht möglich.",
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── 2. Capacity check ───────────────────────────────────────────
|
# ── 2. Capacity check ───────────────────────────────────────────
|
||||||
if plan and plan.maxFeatureInstances is not None:
|
if plan and plan.maxFeatureInstances is not None:
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,8 @@ def _buildDynamicBlock(
|
||||||
mandateId = str(instance.mandateId)
|
mandateId = str(instance.mandateId)
|
||||||
if mandateId not in mandatesMap:
|
if mandateId not in mandatesMap:
|
||||||
mandate = rootInterface.getMandate(mandateId)
|
mandate = rootInterface.getMandate(mandateId)
|
||||||
|
if not mandate or not getattr(mandate, "enabled", True):
|
||||||
|
continue
|
||||||
mandateName = (mandate.label or mandate.name) if mandate else mandateId
|
mandateName = (mandate.label or mandate.name) if mandate else mandateId
|
||||||
mandatesMap[mandateId] = {
|
mandatesMap[mandateId] = {
|
||||||
"id": mandateId,
|
"id": mandateId,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue