311 lines
13 KiB
Python
311 lines
13 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
View models for the /api/attributes/ endpoint.
|
|
|
|
These extend base DB models with computed / enriched fields that the gateway
|
|
adds at response time (JOINs, aggregations, synthetics). They are NEVER used
|
|
for DB operations — only for ``getModelAttributeDefinitions()`` so the frontend
|
|
can resolve column types via ``resolveColumnTypes`` without hardcoding.
|
|
|
|
Naming convention: ``{BaseModel}View``.
|
|
|
|
``getModelClasses()`` in ``attributeUtils.py`` auto-discovers every
|
|
``datamodel*.py`` under ``modules/datamodels/`` — so placing them here is
|
|
sufficient for registration.
|
|
"""
|
|
|
|
from typing import Optional, List
|
|
from pydantic import Field
|
|
|
|
from modules.datamodels.datamodelBase import MODEL_REGISTRY, PowerOnModel
|
|
from modules.datamodels.datamodelMembership import UserMandate, FeatureAccess
|
|
from modules.datamodels.datamodelBilling import BillingTransaction
|
|
from modules.datamodels.datamodelSubscription import MandateSubscription
|
|
from modules.datamodels.datamodelUiLanguage import UiLanguageSet
|
|
from modules.datamodels.datamodelRbac import Role
|
|
from modules.features.neutralization.datamodelFeatureNeutralizer import DataNeutralizerAttributes
|
|
from modules.shared.i18nRegistry import i18nModel
|
|
|
|
|
|
# ============================================================================
|
|
# Punkt 1a: UserMandate + enriched user fields
|
|
# ============================================================================
|
|
|
|
@i18nModel("Benutzer-Mandant (Ansicht)")
|
|
class UserMandateView(UserMandate):
|
|
"""UserMandate erweitert um aufgeloeste Benutzerfelder und Rollenlabels."""
|
|
|
|
username: Optional[str] = Field(
|
|
default=None,
|
|
description="Username (resolved from userId)",
|
|
json_schema_extra={"label": "Benutzername", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
email: Optional[str] = Field(
|
|
default=None,
|
|
description="E-Mail address (resolved from userId)",
|
|
json_schema_extra={"label": "E-Mail", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
fullName: Optional[str] = Field(
|
|
default=None,
|
|
description="Full name (resolved from userId)",
|
|
json_schema_extra={"label": "Vollstaendiger Name", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
roleLabels: Optional[List[str]] = Field(
|
|
default=None,
|
|
description="Role labels (resolved from junction table)",
|
|
json_schema_extra={"label": "Rollen", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Punkt 1b: FeatureAccess + enriched user fields
|
|
# ============================================================================
|
|
|
|
@i18nModel("Feature-Zugang (Ansicht)")
|
|
class FeatureAccessView(FeatureAccess):
|
|
"""FeatureAccess erweitert um aufgeloeste Benutzerfelder und Rollenlabels."""
|
|
|
|
username: Optional[str] = Field(
|
|
default=None,
|
|
description="Username (resolved from userId)",
|
|
json_schema_extra={"label": "Benutzername", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
email: Optional[str] = Field(
|
|
default=None,
|
|
description="E-Mail address (resolved from userId)",
|
|
json_schema_extra={"label": "E-Mail", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
fullName: Optional[str] = Field(
|
|
default=None,
|
|
description="Full name (resolved from userId)",
|
|
json_schema_extra={"label": "Vollstaendiger Name", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
roleLabels: Optional[List[str]] = Field(
|
|
default=None,
|
|
description="Role labels (resolved from junction table)",
|
|
json_schema_extra={"label": "Rollen", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Punkt 1d: BillingTransaction + enriched mandate/user names
|
|
# ============================================================================
|
|
|
|
@i18nModel("Transaktion (Ansicht)")
|
|
class BillingTransactionView(BillingTransaction):
|
|
"""BillingTransaction erweitert um aufgeloeste Mandanten-/Benutzernamen."""
|
|
|
|
mandateName: Optional[str] = Field(
|
|
default=None,
|
|
description="Mandate name (resolved from accountId/mandateId)",
|
|
json_schema_extra={"label": "Mandant", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
userName: Optional[str] = Field(
|
|
default=None,
|
|
description="User name (resolved from createdByUserId)",
|
|
json_schema_extra={"label": "Benutzer", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Punkt 3a: MandateSubscription + aggregated fields
|
|
# ============================================================================
|
|
|
|
@i18nModel("Abonnement (Ansicht)")
|
|
class MandateSubscriptionView(MandateSubscription):
|
|
"""MandateSubscription erweitert um aggregierte Laufzeitwerte."""
|
|
|
|
mandateName: Optional[str] = Field(
|
|
default=None,
|
|
description="Mandate name (resolved from mandateId)",
|
|
json_schema_extra={"label": "Mandant", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
planTitle: Optional[str] = Field(
|
|
default=None,
|
|
description="Plan title (resolved from planKey)",
|
|
json_schema_extra={"label": "Plan", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
activeUsers: Optional[int] = Field(
|
|
default=None,
|
|
description="Number of active users in the mandate",
|
|
json_schema_extra={"label": "Benutzer", "frontend_type": "number", "frontend_readonly": True},
|
|
)
|
|
activeInstances: Optional[int] = Field(
|
|
default=None,
|
|
description="Number of active feature instances in the mandate",
|
|
json_schema_extra={"label": "Module", "frontend_type": "number", "frontend_readonly": True},
|
|
)
|
|
monthlyRevenueCHF: Optional[float] = Field(
|
|
default=None,
|
|
description="Calculated monthly revenue in CHF",
|
|
json_schema_extra={"label": "Umsatz pro Monat", "frontend_type": "number", "frontend_readonly": True},
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Punkt 3b: UiLanguageSet + computed counts
|
|
# ============================================================================
|
|
|
|
@i18nModel("Sprachset (Ansicht)")
|
|
class UiLanguageSetView(UiLanguageSet):
|
|
"""UiLanguageSet erweitert um berechnete Uebersetzungszaehler."""
|
|
|
|
uiCount: Optional[int] = Field(
|
|
default=None,
|
|
description="Number of UI translation entries",
|
|
json_schema_extra={"label": "UI", "frontend_type": "number", "frontend_readonly": True},
|
|
)
|
|
gatewayCount: Optional[int] = Field(
|
|
default=None,
|
|
description="Number of gateway/API translation entries",
|
|
json_schema_extra={"label": "API", "frontend_type": "number", "frontend_readonly": True},
|
|
)
|
|
entriesCount: Optional[int] = Field(
|
|
default=None,
|
|
description="Total number of translation entries",
|
|
json_schema_extra={"label": "Gesamt", "frontend_type": "number", "frontend_readonly": True},
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Punkt 1c: DataNeutralizerAttributes + enriched fields
|
|
#
|
|
# DataNeutralizerAttributes extends BaseModel (not PowerOnModel), so its
|
|
# subclass does NOT auto-register in MODEL_REGISTRY. We register manually.
|
|
# ============================================================================
|
|
|
|
@i18nModel("Neutralisierungs-Zuordnung (Ansicht)")
|
|
class DataNeutralizerAttributesView(DataNeutralizerAttributes):
|
|
"""DataNeutralizerAttributes erweitert um synthetische/aufgeloeste Felder."""
|
|
|
|
placeholder: Optional[str] = Field(
|
|
default=None,
|
|
description="Synthetic placeholder string [patternType.id]",
|
|
json_schema_extra={"label": "Platzhalter", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
username: Optional[str] = Field(
|
|
default=None,
|
|
description="Username (resolved from userId)",
|
|
json_schema_extra={"label": "Benutzer", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
instanceLabel: Optional[str] = Field(
|
|
default=None,
|
|
description="Feature instance label (resolved from featureInstanceId)",
|
|
json_schema_extra={"label": "Feature-Instanz", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
|
|
|
|
# Manual registration for non-PowerOnModel view
|
|
MODEL_REGISTRY["DataNeutralizerAttributesView"] = DataNeutralizerAttributesView # type: ignore[assignment]
|
|
|
|
|
|
# ============================================================================
|
|
# Role view — admin RBAC list with computed `scopeType` + `userCount`
|
|
#
|
|
# `scopeType` is computed in the route from (mandateId, isSystemRole). Exposed
|
|
# here as a pure `select` field so the frontend renders the user-facing label
|
|
# from `frontend_options` (no hardcoded mapping in the page).
|
|
# ============================================================================
|
|
|
|
@i18nModel("Rolle (Ansicht)")
|
|
class RoleView(Role):
|
|
"""Role extended with computed scope information for the admin UI."""
|
|
|
|
scopeType: Optional[str] = Field(
|
|
default=None,
|
|
description="Computed scope: 'system' (template), 'global', or 'mandate'.",
|
|
json_schema_extra={
|
|
"label": "Geltungsbereich",
|
|
"frontend_type": "select",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
"frontend_options": [
|
|
{"value": "system", "label": "System-Template"},
|
|
{"value": "global", "label": "Template"},
|
|
{"value": "mandate", "label": "Mandant"},
|
|
],
|
|
},
|
|
)
|
|
userCount: Optional[int] = Field(
|
|
default=None,
|
|
description="Number of users assigned to this role (via UserMandateRole).",
|
|
json_schema_extra={
|
|
"label": "Benutzer",
|
|
"frontend_type": "number",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
},
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Automation Workflow — dashboard view with synthesized fields
|
|
# ============================================================================
|
|
|
|
from modules.features.graphicalEditor.datamodelFeatureGraphicalEditor import AutoWorkflow
|
|
|
|
|
|
@i18nModel("Workflow (Ansicht)")
|
|
class Automation2WorkflowView(AutoWorkflow):
|
|
"""AutoWorkflow extended with computed dashboard fields.
|
|
|
|
Used exclusively for /api/attributes/ so the frontend can resolve column
|
|
types for the workflow dashboard table (FormGeneratorTable).
|
|
"""
|
|
|
|
sysCreatedAt: Optional[float] = Field(
|
|
default=None,
|
|
description="Record creation timestamp (UTC)",
|
|
json_schema_extra={
|
|
"label": "Erstellt",
|
|
"frontend_type": "timestamp",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
},
|
|
)
|
|
lastStartedAt: Optional[float] = Field(
|
|
default=None,
|
|
description="Timestamp of the most recent workflow run start",
|
|
json_schema_extra={
|
|
"label": "Zuletzt gestartet",
|
|
"frontend_type": "timestamp",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
},
|
|
)
|
|
runCount: Optional[int] = Field(
|
|
default=None,
|
|
description="Total number of runs for this workflow",
|
|
json_schema_extra={
|
|
"label": "Laeufe",
|
|
"frontend_type": "number",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
},
|
|
)
|
|
mandateLabel: Optional[str] = Field(
|
|
default=None,
|
|
description="Mandate name (resolved from mandateId)",
|
|
json_schema_extra={"label": "Mandant", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
instanceLabel: Optional[str] = Field(
|
|
default=None,
|
|
description="Feature instance label (resolved from featureInstanceId)",
|
|
json_schema_extra={"label": "Feature-Instanz", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
featureCode: Optional[str] = Field(
|
|
default=None,
|
|
description="Feature code of the owning instance",
|
|
json_schema_extra={"label": "Feature", "frontend_type": "text", "frontend_readonly": True},
|
|
)
|
|
isRunning: Optional[bool] = Field(
|
|
default=None,
|
|
description="Whether the workflow currently has an active run",
|
|
json_schema_extra={
|
|
"label": "Läuft",
|
|
"frontend_type": "checkbox",
|
|
"frontend_readonly": True,
|
|
"frontend_format_labels": ["Ja", "-", "Nein"],
|
|
},
|
|
)
|