348 lines
15 KiB
Python
348 lines
15 KiB
Python
"""
|
|
Investor Demo April 2026
|
|
|
|
Creates a complete demo environment with two mandates, one user,
|
|
and all feature instances needed for the investor live demo.
|
|
|
|
Mandates:
|
|
- HappyLife AG (happylife) — workspace, trustee(RMA), graphEditor, chatbot, neutralization
|
|
- Alpina Treuhand AG (alpina) — workspace, trustee(RMA), graphEditor, neutralization
|
|
|
|
User:
|
|
- Patrick Helvetia (p.motsch@poweron.swiss) — SysAdmin, member of both mandates
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import uuid
|
|
from typing import Dict, Any, Optional, List
|
|
|
|
from modules.demoConfigs._baseDemoConfig import _BaseDemoConfig
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_DEMO_PREFIX = "demo-inv2026"
|
|
|
|
_MANDATE_HAPPYLIFE = {
|
|
"name": "happylife",
|
|
"label": "HappyLife AG",
|
|
}
|
|
|
|
_MANDATE_ALPINA = {
|
|
"name": "alpina-treuhand",
|
|
"label": "Alpina Treuhand AG",
|
|
}
|
|
|
|
_USER = {
|
|
"username": "patrick.helvetia",
|
|
"email": "p.motsch@poweron.swiss",
|
|
"fullName": "Patrick Helvetia",
|
|
"password": "patrick.helvetia",
|
|
"language": "en",
|
|
}
|
|
|
|
_FEATURES_HAPPYLIFE = ["workspace", "trustee", "graphicalEditor", "chatbot", "neutralization"]
|
|
_FEATURES_ALPINA = ["workspace", "trustee", "graphicalEditor", "neutralization"]
|
|
|
|
|
|
class InvestorDemo2026(_BaseDemoConfig):
|
|
code = "investor-demo-2026"
|
|
label = "Investor Demo April 2026"
|
|
description = (
|
|
"Two mandates (HappyLife AG + Alpina Treuhand AG), one SysAdmin user, "
|
|
"trustee with RMA, workspace, graph editor, chatbot, and neutralization."
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# load
|
|
# ------------------------------------------------------------------
|
|
def load(self, db) -> Dict[str, Any]:
|
|
summary: Dict[str, Any] = {"created": [], "skipped": [], "errors": []}
|
|
|
|
try:
|
|
mandateIdHappy = self._ensureMandate(db, _MANDATE_HAPPYLIFE, summary)
|
|
mandateIdAlpina = self._ensureMandate(db, _MANDATE_ALPINA, summary)
|
|
|
|
userId = self._ensureUser(db, summary)
|
|
|
|
if mandateIdHappy:
|
|
self._ensureMembership(db, userId, mandateIdHappy, _MANDATE_HAPPYLIFE["label"], summary)
|
|
self._ensureFeatures(db, mandateIdHappy, _MANDATE_HAPPYLIFE["label"], _FEATURES_HAPPYLIFE, summary)
|
|
|
|
if mandateIdAlpina:
|
|
self._ensureMembership(db, userId, mandateIdAlpina, _MANDATE_ALPINA["label"], summary)
|
|
self._ensureFeatures(db, mandateIdAlpina, _MANDATE_ALPINA["label"], _FEATURES_ALPINA, summary)
|
|
|
|
self._ensureTrusteeRmaConfig(db, mandateIdHappy, _MANDATE_HAPPYLIFE["label"], summary)
|
|
self._ensureTrusteeRmaConfig(db, mandateIdAlpina, _MANDATE_ALPINA["label"], summary)
|
|
|
|
self._ensureNeutralizationConfig(db, mandateIdHappy, summary)
|
|
self._ensureNeutralizationConfig(db, mandateIdAlpina, summary)
|
|
|
|
self._ensureBilling(db, mandateIdHappy, _MANDATE_HAPPYLIFE["label"], summary)
|
|
self._ensureBilling(db, mandateIdAlpina, _MANDATE_ALPINA["label"], summary)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Demo load failed: {e}", exc_info=True)
|
|
summary["errors"].append(str(e))
|
|
|
|
return summary
|
|
|
|
# ------------------------------------------------------------------
|
|
# remove
|
|
# ------------------------------------------------------------------
|
|
def remove(self, db) -> Dict[str, Any]:
|
|
summary: Dict[str, Any] = {"removed": [], "errors": []}
|
|
|
|
from modules.datamodels.datamodelUam import Mandate, UserInDB
|
|
from modules.datamodels.datamodelMembership import UserMandate
|
|
|
|
for mandateDef in [_MANDATE_HAPPYLIFE, _MANDATE_ALPINA]:
|
|
try:
|
|
existing = db.getRecordset(Mandate, recordFilter={"name": mandateDef["name"]})
|
|
for m in existing:
|
|
mid = m.get("id")
|
|
db.recordDelete(Mandate, mid)
|
|
summary["removed"].append(f"Mandate {mandateDef['label']} ({mid})")
|
|
logger.info(f"Removed mandate {mandateDef['label']} ({mid})")
|
|
except Exception as e:
|
|
summary["errors"].append(f"Remove mandate {mandateDef['label']}: {e}")
|
|
|
|
try:
|
|
existing = db.getRecordset(UserInDB, recordFilter={"username": _USER["username"]})
|
|
for u in existing:
|
|
uid = u.get("id")
|
|
memberships = db.getRecordset(UserMandate, recordFilter={"userId": uid})
|
|
for mem in memberships:
|
|
try:
|
|
db.recordDelete(UserMandate, mem.get("id"))
|
|
except Exception:
|
|
pass
|
|
db.recordDelete(UserInDB, uid)
|
|
summary["removed"].append(f"User {_USER['username']} ({uid})")
|
|
logger.info(f"Removed user {_USER['username']} ({uid})")
|
|
except Exception as e:
|
|
summary["errors"].append(f"Remove user: {e}")
|
|
|
|
self._removeLanguageSet(db, "es", summary)
|
|
|
|
return summary
|
|
|
|
# ------------------------------------------------------------------
|
|
# helpers
|
|
# ------------------------------------------------------------------
|
|
|
|
def _ensureMandate(self, db, mandateDef: Dict, summary: Dict) -> Optional[str]:
|
|
from modules.datamodels.datamodelUam import Mandate
|
|
from modules.interfaces.interfaceBootstrap import copySystemRolesToMandate
|
|
|
|
existing = db.getRecordset(Mandate, recordFilter={"name": mandateDef["name"]})
|
|
if existing:
|
|
mid = existing[0].get("id")
|
|
summary["skipped"].append(f"Mandate {mandateDef['label']} exists ({mid})")
|
|
return mid
|
|
|
|
mandate = Mandate(name=mandateDef["name"], label=mandateDef["label"], enabled=True)
|
|
created = db.recordCreate(Mandate, mandate)
|
|
mid = created.get("id")
|
|
logger.info(f"Created mandate {mandateDef['label']} ({mid})")
|
|
summary["created"].append(f"Mandate {mandateDef['label']}")
|
|
|
|
copySystemRolesToMandate(db, mid)
|
|
return mid
|
|
|
|
def _ensureUser(self, db, summary: Dict) -> Optional[str]:
|
|
from modules.datamodels.datamodelUam import UserInDB, AuthAuthority
|
|
from passlib.context import CryptContext
|
|
|
|
existing = db.getRecordset(UserInDB, recordFilter={"username": _USER["username"]})
|
|
if existing:
|
|
uid = existing[0].get("id")
|
|
summary["skipped"].append(f"User {_USER['username']} exists ({uid})")
|
|
return uid
|
|
|
|
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
|
|
user = UserInDB(
|
|
username=_USER["username"],
|
|
email=_USER["email"],
|
|
fullName=_USER["fullName"],
|
|
enabled=True,
|
|
language=_USER["language"],
|
|
isSysAdmin=True,
|
|
authenticationAuthority=AuthAuthority.LOCAL,
|
|
hashedPassword=pwdContext.hash(_USER["password"]),
|
|
)
|
|
created = db.recordCreate(UserInDB, user)
|
|
uid = created.get("id")
|
|
logger.info(f"Created user {_USER['username']} ({uid})")
|
|
summary["created"].append(f"User {_USER['fullName']}")
|
|
return uid
|
|
|
|
def _ensureMembership(self, db, userId: str, mandateId: str, mandateLabel: str, summary: Dict):
|
|
from modules.datamodels.datamodelMembership import UserMandate, UserMandateRole, Role
|
|
|
|
existing = db.getRecordset(UserMandate, recordFilter={"userId": userId, "mandateId": mandateId})
|
|
if existing:
|
|
userMandateId = existing[0].get("id")
|
|
summary["skipped"].append(f"Membership {_USER['username']} -> {mandateLabel} exists")
|
|
else:
|
|
um = UserMandate(userId=userId, mandateId=mandateId, enabled=True)
|
|
created = db.recordCreate(UserMandate, um)
|
|
userMandateId = created.get("id")
|
|
summary["created"].append(f"Membership {_USER['username']} -> {mandateLabel}")
|
|
logger.info(f"Created membership {_USER['username']} -> {mandateLabel}")
|
|
|
|
adminRoles = db.getRecordset(Role, recordFilter={"mandateId": mandateId, "label": "admin"})
|
|
if adminRoles:
|
|
adminRoleId = adminRoles[0].get("id")
|
|
existingRole = db.getRecordset(UserMandateRole, recordFilter={"userMandateId": userMandateId, "roleId": adminRoleId})
|
|
if not existingRole:
|
|
umr = UserMandateRole(userMandateId=userMandateId, roleId=adminRoleId)
|
|
db.recordCreate(UserMandateRole, umr)
|
|
logger.info(f"Assigned admin role in {mandateLabel}")
|
|
|
|
def _ensureFeatures(self, db, mandateId: str, mandateLabel: str, featureCodes: List[str], summary: Dict):
|
|
from modules.interfaces.interfaceFeatures import getFeatureInterface
|
|
|
|
fi = getFeatureInterface(db)
|
|
existingInstances = fi.getFeatureInstances(mandateId)
|
|
existingCodes = {
|
|
(inst.featureCode if hasattr(inst, "featureCode") else inst.get("featureCode", ""))
|
|
for inst in existingInstances
|
|
}
|
|
|
|
for code in featureCodes:
|
|
if code in existingCodes:
|
|
summary["skipped"].append(f"Feature {code} in {mandateLabel} exists")
|
|
continue
|
|
try:
|
|
fi.createFeatureInstance(
|
|
featureCode=code,
|
|
mandateId=mandateId,
|
|
label=f"{code} ({mandateLabel})",
|
|
enabled=True,
|
|
copyTemplateRoles=True,
|
|
)
|
|
summary["created"].append(f"Feature {code} in {mandateLabel}")
|
|
logger.info(f"Created feature instance {code} in {mandateLabel}")
|
|
except Exception as e:
|
|
summary["errors"].append(f"Feature {code} in {mandateLabel}: {e}")
|
|
logger.error(f"Failed to create feature {code} in {mandateLabel}: {e}")
|
|
|
|
def _ensureTrusteeRmaConfig(self, db, mandateId: Optional[str], mandateLabel: str, summary: Dict):
|
|
if not mandateId:
|
|
return
|
|
|
|
from modules.datamodels.datamodelFeatures import FeatureInstance
|
|
from modules.features.trustee.datamodelFeatureTrustee import TrusteeAccountingConfig
|
|
from modules.shared.configuration import APP_CONFIG, encryptValue
|
|
|
|
instances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": mandateId, "featureCode": "trustee"})
|
|
if not instances:
|
|
summary["skipped"].append(f"No trustee instance in {mandateLabel} for RMA config")
|
|
return
|
|
|
|
instanceId = instances[0].get("id")
|
|
|
|
existing = db.getRecordset(TrusteeAccountingConfig, recordFilter={"featureInstanceId": instanceId})
|
|
if existing:
|
|
summary["skipped"].append(f"RMA config for {mandateLabel} exists")
|
|
return
|
|
|
|
apiBaseUrl = APP_CONFIG.get("Demo_RMA_ApiBaseUrl", "")
|
|
clientName = APP_CONFIG.get("Demo_RMA_ClientName", "")
|
|
apiKey = APP_CONFIG.get("Demo_RMA_ApiKey", "")
|
|
|
|
if not apiBaseUrl or not apiKey:
|
|
summary["errors"].append(
|
|
f"RMA credentials missing in config.ini (Demo_RMA_ApiBaseUrl, Demo_RMA_ClientName, Demo_RMA_ApiKey) for {mandateLabel}"
|
|
)
|
|
return
|
|
|
|
plainConfig = {
|
|
"apiBaseUrl": apiBaseUrl,
|
|
"clientName": clientName,
|
|
"apiKey": apiKey,
|
|
}
|
|
|
|
configRecord = {
|
|
"id": str(uuid.uuid4()),
|
|
"featureInstanceId": instanceId,
|
|
"connectorType": "rma",
|
|
"displayLabel": "Run My Accounts",
|
|
"encryptedConfig": encryptValue(json.dumps(plainConfig), keyName="accountingConfig"),
|
|
"isActive": True,
|
|
"mandateId": mandateId,
|
|
}
|
|
db.recordCreate(TrusteeAccountingConfig, configRecord)
|
|
summary["created"].append(f"RMA accounting config for {mandateLabel}")
|
|
logger.info(f"Created RMA accounting config for {mandateLabel}")
|
|
|
|
def _ensureNeutralizationConfig(self, db, mandateId: Optional[str], summary: Dict):
|
|
if not mandateId:
|
|
return
|
|
|
|
from modules.datamodels.datamodelFeatures import FeatureInstance
|
|
|
|
instances = db.getRecordset(FeatureInstance, recordFilter={"mandateId": mandateId, "featureCode": "neutralization"})
|
|
if not instances:
|
|
return
|
|
|
|
instanceId = instances[0].get("id")
|
|
|
|
try:
|
|
from modules.features.neutralization.datamodelFeatureNeutralizer import DataNeutraliserConfig
|
|
|
|
existing = db.getRecordset(DataNeutraliserConfig, recordFilter={"featureInstanceId": instanceId})
|
|
if existing:
|
|
summary["skipped"].append(f"Neutralization config for mandate {mandateId} exists")
|
|
return
|
|
|
|
config = DataNeutraliserConfig(
|
|
featureInstanceId=instanceId,
|
|
mandateId=mandateId,
|
|
enabled=True,
|
|
scope="featureInstance",
|
|
)
|
|
db.recordCreate(DataNeutraliserConfig, config)
|
|
summary["created"].append(f"Neutralization config for mandate {mandateId}")
|
|
logger.info(f"Created neutralization config for mandate {mandateId}")
|
|
except Exception as e:
|
|
summary["errors"].append(f"Neutralization config: {e}")
|
|
|
|
def _ensureBilling(self, db, mandateId: Optional[str], mandateLabel: str, summary: Dict):
|
|
if not mandateId:
|
|
return
|
|
try:
|
|
from modules.interfaces.interfaceDbBilling import _getRootInterface
|
|
from modules.datamodels.datamodelBilling import BillingSettings
|
|
|
|
billingInterface = _getRootInterface()
|
|
existingSettings = billingInterface.getSettings(mandateId)
|
|
if existingSettings:
|
|
summary["skipped"].append(f"Billing for {mandateLabel} exists")
|
|
return
|
|
|
|
settings = BillingSettings(
|
|
mandateId=mandateId,
|
|
warningThresholdPercent=10.0,
|
|
notifyOnWarning=True,
|
|
)
|
|
billingInterface.db.recordCreate(BillingSettings, settings)
|
|
summary["created"].append(f"Billing settings for {mandateLabel}")
|
|
logger.info(f"Created billing settings for {mandateLabel}")
|
|
except Exception as e:
|
|
summary["errors"].append(f"Billing for {mandateLabel}: {e}")
|
|
|
|
def _removeLanguageSet(self, db, code: str, summary: Dict):
|
|
"""Remove a language set if it was created during demo (e.g. 'es' from UC4)."""
|
|
try:
|
|
from modules.datamodels.datamodelUiLanguage import UiLanguageSet
|
|
|
|
existing = db.getRecordset(UiLanguageSet, recordFilter={"id": code})
|
|
if existing:
|
|
db.recordDelete(UiLanguageSet, code)
|
|
summary["removed"].append(f"Language set '{code}'")
|
|
logger.info(f"Removed language set '{code}'")
|
|
except Exception as e:
|
|
logger.debug(f"Could not remove language set '{code}': {e}")
|