From 3777839a5c7602a66add023fd4ff81227c3a5363 Mon Sep 17 00:00:00 2001 From: patrick-motsch Date: Sun, 15 Feb 2026 10:44:26 +0100 Subject: [PATCH] feat(billing): scope parameter on /view/statistics endpoint - New query parameter scope: personal/mandate/all - personal: filters to only current user's transactions (ignores admin role) - mandate: filters by mandateId parameter - all: existing RBAC-filtered behavior (default) Co-authored-by: Cursor --- modules/routes/routeBilling.py | 41 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/modules/routes/routeBilling.py b/modules/routes/routeBilling.py index f34acbd5..a3800753 100644 --- a/modules/routes/routeBilling.py +++ b/modules/routes/routeBilling.py @@ -989,16 +989,17 @@ def getUserViewStatistics( period: str = Query(default="month", description="Period: 'day' or 'month'"), year: int = Query(default=None, description="Year"), month: Optional[int] = Query(None, description="Month (1-12, required for period='day')"), + scope: str = Query(default="all", description="Scope: 'personal' (own costs only), 'mandate' (filter by mandateId), 'all' (RBAC-filtered)"), + mandateId: Optional[str] = Query(None, description="Mandate ID filter (used with scope='mandate')"), ctx: RequestContext = Depends(getRequestContext) ) -> ViewStatisticsResponse: """ Get aggregated usage statistics across all user's mandates. - RBAC filtering: - - SysAdmin: statistics across all mandates - - Mandate-Admin: statistics for mandates they administrate - - Feature-Instance-Admin: statistics for their feature instances - - Regular user: only their own usage statistics + Scope: + - personal: only the current user's own transactions (ignores admin role) + - mandate: transactions for a specific mandate (requires mandateId parameter) + - all: RBAC-filtered (SysAdmin sees everything, admin sees mandate, user sees own) - period='month': returns monthly time series for the given year - period='day': returns daily time series for the given month/year @@ -1015,22 +1016,34 @@ def getUserViewStatistics( billingInterface = getBillingInterface(ctx.user, ctx.mandateId) # Evaluate RBAC scope - scope = _getBillingDataScope(ctx.user) + rbacScope = _getBillingDataScope(ctx.user) # Determine mandate IDs for data loading - if scope.isGlobalAdmin: - mandateIds = None + if rbacScope.isGlobalAdmin: + loadMandateIds = None else: - mandateIds = scope.adminMandateIds + scope.memberMandateIds - if not mandateIds: + loadMandateIds = rbacScope.adminMandateIds + rbacScope.memberMandateIds + if not loadMandateIds: logger.warning("No mandate IDs found for user") return ViewStatisticsResponse() - # Get all transactions - allTransactions = billingInterface.getUserTransactionsForMandates(mandateIds, limit=10000) + # Scope=mandate: restrict to specific mandate + if scope == "mandate" and mandateId: + loadMandateIds = [mandateId] - # Apply RBAC filter - allTransactions = _filterTransactionsByScope(allTransactions, scope) + # Get all transactions + allTransactions = billingInterface.getUserTransactionsForMandates(loadMandateIds, limit=10000) + + # Apply RBAC filter (respects admin/user roles) + allTransactions = _filterTransactionsByScope(allTransactions, rbacScope) + + # Scope=personal: further filter to only own transactions + if scope == "personal": + userId = str(ctx.user.id) + allTransactions = [ + t for t in allTransactions + if (t.get("createdByUserId") or t.get("userId")) == userId + ] logger.info(f"View statistics: {len(allTransactions)} RBAC-filtered transactions for period={period}, year={year}, month={month}")