enhanced generic navigation tree

This commit is contained in:
patrick-motsch 2026-02-10 00:10:07 +01:00
parent d98c31a4d1
commit ab48e2e853
9 changed files with 44 additions and 15 deletions

View file

@ -28,7 +28,7 @@ class UserMandate(BaseModel):
) )
mandateId: str = Field( mandateId: str = Field(
description="FK → Mandate.id (CASCADE DELETE)", description="FK → Mandate.id (CASCADE DELETE)",
json_schema_extra={"frontend_type": "select", "frontend_readonly": False, "frontend_required": True, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "name"} json_schema_extra={"frontend_type": "select", "frontend_readonly": False, "frontend_required": True, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "label"}
) )
enabled: bool = Field( enabled: bool = Field(
default=True, default=True,

View file

@ -55,7 +55,7 @@ class Role(BaseModel):
mandateId: Optional[str] = Field( mandateId: Optional[str] = Field(
default=None, default=None,
description="FK → Mandate.id (CASCADE DELETE). Null = Global/Template role.", description="FK → Mandate.id (CASCADE DELETE). Null = Global/Template role.",
json_schema_extra={"frontend_type": "select", "frontend_readonly": True, "frontend_visible": True, "frontend_required": False, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "name"} json_schema_extra={"frontend_type": "select", "frontend_readonly": True, "frontend_visible": True, "frontend_required": False, "frontend_fk_source": "/api/mandates/", "frontend_fk_display_field": "label"}
) )
featureInstanceId: Optional[str] = Field( featureInstanceId: Optional[str] = Field(
default=None, default=None,

View file

@ -73,10 +73,10 @@ class Mandate(BaseModel):
description="Name of the mandate", description="Name of the mandate",
json_schema_extra={"frontend_type": "text", "frontend_readonly": False, "frontend_required": True} json_schema_extra={"frontend_type": "text", "frontend_readonly": False, "frontend_required": True}
) )
description: Optional[str] = Field( label: Optional[str] = Field(
default=None, default=None,
description="Description of the mandate", description="Display label of the mandate",
json_schema_extra={"frontend_type": "textarea", "frontend_readonly": False, "frontend_required": False} json_schema_extra={"frontend_type": "text", "frontend_readonly": False, "frontend_required": False}
) )
enabled: bool = Field( enabled: bool = Field(
default=True, default=True,
@ -104,7 +104,7 @@ registerModelLabels(
{ {
"id": {"en": "ID", "de": "ID", "fr": "ID"}, "id": {"en": "ID", "de": "ID", "fr": "ID"},
"name": {"en": "Name", "de": "Name", "fr": "Nom"}, "name": {"en": "Name", "de": "Name", "fr": "Nom"},
"description": {"en": "Description", "de": "Beschreibung", "fr": "Description"}, "label": {"en": "Label", "de": "Label", "fr": "Libellé"},
"enabled": {"en": "Enabled", "de": "Aktiviert", "fr": "Activé"}, "enabled": {"en": "Enabled", "de": "Aktiviert", "fr": "Activé"},
"isSystem": {"en": "System Mandate", "de": "System-Mandant", "fr": "Mandat système"}, "isSystem": {"en": "System Mandate", "de": "System-Mandant", "fr": "Mandat système"},
}, },

View file

@ -51,6 +51,9 @@ def initBootstrap(db: DatabaseConnector) -> None:
# Initialize root mandate # Initialize root mandate
mandateId = initRootMandate(db) mandateId = initRootMandate(db)
# Migrate existing mandate records: description -> label
_migrateMandateDescriptionToLabel(db)
# Initialize system role TEMPLATES (mandateId=None, isSystemRole=True) # Initialize system role TEMPLATES (mandateId=None, isSystemRole=True)
initRoles(db) initRoles(db)
@ -276,6 +279,32 @@ def initRootMandate(db: DatabaseConnector) -> Optional[str]:
return mandateId return mandateId
def _migrateMandateDescriptionToLabel(db: DatabaseConnector) -> None:
"""
Migration: Rename 'description' field to 'label' in all Mandate records.
Copies existing 'description' values to 'label' and removes the old field.
Safe to run multiple times (idempotent).
"""
allMandates = db.getRecordset(Mandate)
migratedCount = 0
for mandateRecord in allMandates:
mandateId = mandateRecord.get("id")
hasDescription = "description" in mandateRecord and mandateRecord.get("description") is not None
hasLabel = "label" in mandateRecord and mandateRecord.get("label") is not None
if hasDescription and not hasLabel:
# Copy description to label
updateData = {"label": mandateRecord["description"]}
db.recordModify(Mandate, mandateId, updateData)
migratedCount += 1
logger.info(f"Migrated mandate {mandateId}: description -> label")
if migratedCount > 0:
logger.info(f"Migrated {migratedCount} mandate(s) from description to label")
else:
logger.debug("No mandate description->label migration needed")
def initAdminUser(db: DatabaseConnector, mandateId: Optional[str]) -> Optional[str]: def initAdminUser(db: DatabaseConnector, mandateId: Optional[str]) -> Optional[str]:
""" """
Creates the Admin user if it doesn't exist. Creates the Admin user if it doesn't exist.

View file

@ -1444,7 +1444,7 @@ class AppObjects:
return Mandate(**filteredMandates[0]) return Mandate(**filteredMandates[0])
def createMandate(self, name: str, description: str = None, enabled: bool = True) -> Mandate: def createMandate(self, name: str, label: str = None, enabled: bool = True) -> Mandate:
""" """
Creates a new mandate if user has permission. Creates a new mandate if user has permission.
Automatically copies system template roles (admin, user, viewer) to the new mandate. Automatically copies system template roles (admin, user, viewer) to the new mandate.
@ -1453,7 +1453,7 @@ class AppObjects:
raise PermissionError("No permission to create mandates") raise PermissionError("No permission to create mandates")
# Create mandate data using model # Create mandate data using model
mandateData = Mandate(name=name, description=description, enabled=enabled) mandateData = Mandate(name=name, label=label, enabled=enabled)
# Create mandate record # Create mandate record
createdRecord = self.db.recordCreate(Mandate, mandateData) createdRecord = self.db.recordCreate(Mandate, mandateData)

View file

@ -355,7 +355,7 @@ def getBalanceForMandate(
from modules.interfaces.interfaceDbApp import getInterface as getAppInterface from modules.interfaces.interfaceDbApp import getInterface as getAppInterface
appInterface = getAppInterface(ctx.user, mandateId=targetMandateId) appInterface = getAppInterface(ctx.user, mandateId=targetMandateId)
mandate = appInterface.getMandate(targetMandateId) mandate = appInterface.getMandate(targetMandateId)
mandateName = mandate.get("name", "") if mandate else "" mandateName = (mandate.get("label") or mandate.get("name", "")) if mandate else ""
return BillingBalanceResponse( return BillingBalanceResponse(
mandateId=targetMandateId, mandateId=targetMandateId,

View file

@ -192,7 +192,7 @@ def create_mandate(
) )
# Get optional fields with defaults # Get optional fields with defaults
description = mandateData.get('description') label = mandateData.get('label')
enabled = mandateData.get('enabled', True) enabled = mandateData.get('enabled', True)
appInterface = interfaceDbApp.getRootInterface() appInterface = interfaceDbApp.getRootInterface()
@ -200,7 +200,7 @@ def create_mandate(
# Create mandate # Create mandate
newMandate = appInterface.createMandate( newMandate = appInterface.createMandate(
name=name, name=name,
description=description, label=label,
enabled=enabled enabled=enabled
) )

View file

@ -190,7 +190,7 @@ def create_invitation(
from modules.connectors.connectorMessagingEmail import ConnectorMessagingEmail from modules.connectors.connectorMessagingEmail import ConnectorMessagingEmail
# Get mandate name for the email # Get mandate name for the email
mandate = rootInterface.getMandate(str(context.mandateId)) mandate = rootInterface.getMandate(str(context.mandateId))
mandateName = mandate.name if mandate else "PowerOn" mandateName = (mandate.label or mandate.name) if mandate else "PowerOn"
emailConnector = ConnectorMessagingEmail() emailConnector = ConnectorMessagingEmail()
emailSubject = f"Einladung zu {mandateName}" emailSubject = f"Einladung zu {mandateName}"
@ -249,7 +249,7 @@ def create_invitation(
# Get mandate name for notification # Get mandate name for notification
mandate = rootInterface.getMandate(str(context.mandateId)) mandate = rootInterface.getMandate(str(context.mandateId))
mandateName = mandate.mandateLabel if mandate and mandate.mandateLabel else "PowerOn" mandateName = (mandate.label or mandate.name) if mandate else "PowerOn"
inviterName = context.user.fullName or context.user.username inviterName = context.user.fullName or context.user.username
createInvitationNotification( createInvitationNotification(
@ -529,7 +529,7 @@ def validate_invitation(
# Get mandate name # Get mandate name
mandate = rootInterface.getMandate(str(mandateId)) if mandateId else None mandate = rootInterface.getMandate(str(mandateId)) if mandateId else None
if mandate: if mandate:
mandateName = mandate.name mandateName = mandate.label or mandate.name
# Get role names # Get role names
roleIds = invitation.roleIds or [] roleIds = invitation.roleIds or []

View file

@ -153,7 +153,7 @@ 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)
mandateName = mandate.name if mandate and hasattr(mandate, 'name') else mandateId mandateName = (mandate.label or mandate.name) if mandate else mandateId
mandatesMap[mandateId] = { mandatesMap[mandateId] = {
"id": mandateId, "id": mandateId,
"uiLabel": mandateName, "uiLabel": mandateName,