fix: mandate subscription provisioning, capacity errors, invitations API
Made-with: Cursor
This commit is contained in:
parent
ecbdd1ea74
commit
50bf59879f
4 changed files with 39 additions and 7 deletions
|
|
@ -1990,8 +1990,10 @@ class AppObjects:
|
||||||
cleanedRecord = dict(createdRecord)
|
cleanedRecord = dict(createdRecord)
|
||||||
return UserMandate(**cleanedRecord)
|
return UserMandate(**cleanedRecord)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if e.__class__.__name__ == "SubscriptionCapacityException":
|
||||||
|
raise
|
||||||
logger.error(f"Error creating UserMandate: {e}")
|
logger.error(f"Error creating UserMandate: {e}")
|
||||||
raise ValueError(f"Failed to create UserMandate: {e}")
|
raise ValueError(f"Failed to create UserMandate: {e}") from e
|
||||||
|
|
||||||
def _ensureUserBillingAccount(self, userId: str, mandateId: str) -> None:
|
def _ensureUserBillingAccount(self, userId: str, mandateId: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ from modules.datamodels.datamodelMembership import UserMandate, UserMandateRole
|
||||||
from modules.datamodels.datamodelRbac import Role
|
from modules.datamodels.datamodelRbac import Role
|
||||||
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict
|
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict
|
||||||
from modules.routes.routeNotifications import create_access_change_notification
|
from modules.routes.routeNotifications import create_access_change_notification
|
||||||
|
from modules.serviceCenter.services.serviceSubscription.mainServiceSubscription import SubscriptionCapacityException
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -795,6 +796,11 @@ def add_user_to_mandate(
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
except SubscriptionCapacityException as cap:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail=cap.message,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding user to mandate: {e}")
|
logger.error(f"Error adding user to mandate: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,10 @@ class InvitationCreate(BaseModel):
|
||||||
- Mandate-level: featureInstanceId omitted, roleIds are mandate-level roles (user, viewer, admin)
|
- Mandate-level: featureInstanceId omitted, roleIds are mandate-level roles (user, viewer, admin)
|
||||||
- Feature-instance-level: featureInstanceId required, roleIds are instance-level roles
|
- Feature-instance-level: featureInstanceId required, roleIds are instance-level roles
|
||||||
|
|
||||||
Email is required for new users; targetUsername is optional.
|
|
||||||
At least one of email or targetUsername must be provided.
|
At least one of email or targetUsername must be provided.
|
||||||
"""
|
"""
|
||||||
targetUsername: Optional[str] = Field(None, description="Username of the user to invite (must match on acceptance)")
|
targetUsername: Optional[str] = Field(None, description="Username of the user to invite (must match on acceptance)")
|
||||||
email: Optional[str] = Field(None, description="Email address to send invitation link (required for new users)")
|
email: Optional[str] = Field(None, description="Email address to send invitation link (optional if targetUsername is set)")
|
||||||
featureInstanceId: Optional[str] = Field(None, description="Feature instance to grant access to (optional for mandate-level invitations)")
|
featureInstanceId: Optional[str] = Field(None, description="Feature instance to grant access to (optional for mandate-level invitations)")
|
||||||
roleIds: List[str] = Field(..., description="Role IDs: mandate-level (user, viewer, admin) or instance-level")
|
roleIds: List[str] = Field(..., description="Role IDs: mandate-level (user, viewer, admin) or instance-level")
|
||||||
frontendUrl: str = Field(..., description="Frontend URL for building the invite link (provided by frontend)")
|
frontendUrl: str = Field(..., description="Frontend URL for building the invite link (provided by frontend)")
|
||||||
|
|
|
||||||
|
|
@ -786,15 +786,40 @@ class SubscriptionInactiveException(Exception):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
_SUBSCRIPTION_LIMITS_UI_HINT_DE = (
|
||||||
|
" Details zu Ihrem Abonnement, den enthaltenen Limits und Upgrade-Optionen: "
|
||||||
|
"Menü «Administration» → «Billing» → Registerkarte «Abonnement»."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionCapacityException(Exception):
|
class SubscriptionCapacityException(Exception):
|
||||||
def __init__(self, resourceType: str, currentCount: int, maxAllowed: int, message: Optional[str] = None):
|
def __init__(self, resourceType: str, currentCount: int, maxAllowed: int, message: Optional[str] = None):
|
||||||
self.resourceType = resourceType
|
self.resourceType = resourceType
|
||||||
self.currentCount = currentCount
|
self.currentCount = currentCount
|
||||||
self.maxAllowed = maxAllowed
|
self.maxAllowed = maxAllowed
|
||||||
self.message = message or (
|
if message is not None:
|
||||||
f"Ihr Plan erlaubt maximal {maxAllowed} {'Benutzer' if resourceType == 'users' else 'Feature-Instanzen'} "
|
self.message = message
|
||||||
f"(aktuell {currentCount}). Bitte wechseln Sie zu einem grösseren Plan."
|
elif resourceType == "users":
|
||||||
)
|
self.message = (
|
||||||
|
f"Mit dem aktuellen Abonnement sind für diesen Mandanten höchstens {maxAllowed} "
|
||||||
|
f"Benutzer zulässig (derzeit {currentCount}). "
|
||||||
|
f"Ohne Planwechsel können keine weiteren Benutzer hinzugefügt werden."
|
||||||
|
) + _SUBSCRIPTION_LIMITS_UI_HINT_DE
|
||||||
|
elif resourceType == "featureInstances":
|
||||||
|
self.message = (
|
||||||
|
f"Es sind höchstens {maxAllowed} aktive Feature-Instanzen erlaubt (derzeit {currentCount}). "
|
||||||
|
f"Bitte Abonnement erweitern oder eine Instanz entfernen."
|
||||||
|
) + _SUBSCRIPTION_LIMITS_UI_HINT_DE
|
||||||
|
elif resourceType == "dataVolumeMB":
|
||||||
|
self.message = (
|
||||||
|
f"Das im Abonnement enthaltene Datenvolumen ({maxAllowed} MB) reicht nicht "
|
||||||
|
f"(aktuell ca. {currentCount} MB). Bitte Speicher-Limit oder Plan anpassen."
|
||||||
|
) + _SUBSCRIPTION_LIMITS_UI_HINT_DE
|
||||||
|
else:
|
||||||
|
self.message = (
|
||||||
|
f"Abonnement-Limit überschritten (Ressource «{resourceType}»: "
|
||||||
|
f"aktuell {currentCount}, erlaubt {maxAllowed})."
|
||||||
|
) + _SUBSCRIPTION_LIMITS_UI_HINT_DE
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
def toClientDict(self) -> Dict[str, Any]:
|
def toClientDict(self) -> Dict[str, Any]:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue