From ee15fd64b07bb9b21228d1f6152355cf701740ea Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Mon, 26 Jan 2026 23:48:19 +0100
Subject: [PATCH] fix crosstable trustee
---
.../trustee/interfaceFeatureTrustee.py | 24 +++++++++++++++++--
modules/routes/routeAdminFeatures.py | 24 ++++++++++++++-----
2 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/modules/features/trustee/interfaceFeatureTrustee.py b/modules/features/trustee/interfaceFeatureTrustee.py
index 99553108..b5e08e2b 100644
--- a/modules/features/trustee/interfaceFeatureTrustee.py
+++ b/modules/features/trustee/interfaceFeatureTrustee.py
@@ -1095,7 +1095,8 @@ class TrusteeObjects:
def deleteDocument(self, documentId: str) -> bool:
"""Delete a document.
- Note: organisationId and contractId removed - feature instance IS the organisation.
+ All position-document cross-table entries (TrusteePositionDocument) referencing
+ this document are deleted first, then the document.
"""
# Get existing document to check creator
existingRecords = self.db.getRecordset(TrusteeDocument, recordFilter={"id": documentId})
@@ -1112,6 +1113,7 @@ class TrusteeObjects:
logger.warning(f"User {self.userId} lacks permission to delete document")
return False
+ self._deletePositionDocumentLinksForDocument(documentId)
return self.db.recordDelete(TrusteeDocument, documentId)
# ===== Position CRUD =====
@@ -1259,7 +1261,8 @@ class TrusteeObjects:
def deletePosition(self, positionId: str) -> bool:
"""Delete a position.
- Note: organisationId and contractId removed - feature instance IS the organisation.
+ All position-document cross-table entries (TrusteePositionDocument) referencing
+ this position are deleted first, then the position.
"""
# Get existing position to check creator
existingRecords = self.db.getRecordset(TrusteePosition, recordFilter={"id": positionId})
@@ -1276,6 +1279,7 @@ class TrusteeObjects:
logger.warning(f"User {self.userId} lacks permission to delete position")
return False
+ self._deletePositionDocumentLinksForPosition(positionId)
return self.db.recordDelete(TrusteePosition, positionId)
# ===== Position-Document Link CRUD =====
@@ -1423,6 +1427,22 @@ class TrusteeObjects:
return self.db.recordDelete(TrusteePositionDocument, linkId)
+ def _deletePositionDocumentLinksForDocument(self, documentId: str) -> None:
+ """Delete all position-document cross-table entries referencing this document."""
+ links = self.db.getRecordset(TrusteePositionDocument, recordFilter={"documentId": documentId})
+ for link in links:
+ linkId = link.get("id")
+ if linkId:
+ self.db.recordDelete(TrusteePositionDocument, linkId)
+
+ def _deletePositionDocumentLinksForPosition(self, positionId: str) -> None:
+ """Delete all position-document cross-table entries referencing this position."""
+ links = self.db.getRecordset(TrusteePositionDocument, recordFilter={"positionId": positionId})
+ for link in links:
+ linkId = link.get("id")
+ if linkId:
+ self.db.recordDelete(TrusteePositionDocument, linkId)
+
# ===== Trustee-specific Access Check =====
def getUserAccessForOrganisation(self, userId: str, organisationId: str) -> List[Dict[str, Any]]:
diff --git a/modules/routes/routeAdminFeatures.py b/modules/routes/routeAdminFeatures.py
index 82b796c1..56a79741 100644
--- a/modules/routes/routeAdminFeatures.py
+++ b/modules/routes/routeAdminFeatures.py
@@ -878,6 +878,12 @@ class FeatureInstanceUserResponse(BaseModel):
enabled: bool
+class FeatureInstanceUserUpdate(BaseModel):
+ """Request model for updating a feature instance user (roles and active flag)"""
+ roleIds: List[str] = Field(..., description="Role IDs to assign")
+ enabled: Optional[bool] = Field(None, description="Whether this user's access is active (omit to leave unchanged)")
+
+
@router.get("/instances/{instanceId}/users", response_model=List[FeatureInstanceUserResponse])
@limiter.limit("60/minute")
async def list_feature_instance_users(
@@ -1161,18 +1167,19 @@ async def update_feature_instance_user_roles(
request: Request,
instanceId: str,
userId: str,
- roleIds: List[str],
+ data: FeatureInstanceUserUpdate,
context: RequestContext = Depends(getRequestContext)
) -> Dict[str, Any]:
"""
- Update a user's roles in a feature instance.
+ Update a user's roles and active flag in a feature instance.
Replaces all existing FeatureAccessRole records with new ones.
+ If enabled is provided, updates the FeatureAccess.enabled flag.
Args:
instanceId: FeatureInstance ID
userId: User ID to update
- roleIds: New list of role IDs
+ data: roleIds and optional enabled
"""
try:
rootInterface = getRootInterface()
@@ -1215,6 +1222,10 @@ async def update_feature_instance_user_roles(
featureAccessId = existingAccess[0].get("id")
+ # Update enabled flag if provided
+ if data.enabled is not None:
+ rootInterface.db.recordModify(FeatureAccess, featureAccessId, {"enabled": data.enabled})
+
# Delete existing FeatureAccessRole records
existingRoles = rootInterface.db.getRecordset(
FeatureAccessRole,
@@ -1224,7 +1235,7 @@ async def update_feature_instance_user_roles(
rootInterface.db.recordDelete(FeatureAccessRole, role.get("id"))
# Create new FeatureAccessRole records
- for roleId in roleIds:
+ for roleId in data.roleIds:
featureAccessRole = FeatureAccessRole(
featureAccessId=featureAccessId,
roleId=roleId
@@ -1232,14 +1243,15 @@ async def update_feature_instance_user_roles(
rootInterface.db.recordCreate(FeatureAccessRole, featureAccessRole.model_dump())
logger.info(
- f"User {context.user.id} updated roles for user {userId} in feature instance {instanceId}: {roleIds}"
+ f"User {context.user.id} updated roles for user {userId} in feature instance {instanceId}: {data.roleIds}"
)
return {
"featureAccessId": featureAccessId,
"userId": userId,
"featureInstanceId": instanceId,
- "roleIds": roleIds
+ "roleIds": data.roleIds,
+ "enabled": data.enabled if data.enabled is not None else existingAccess[0].get("enabled", True)
}
except HTTPException: