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 <cursoragent@cursor.com>
This commit is contained in:
patrick-motsch 2026-02-15 10:44:26 +01:00
parent e24ef42617
commit 3777839a5c

View file

@ -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}")