Files
orion/app/modules/billing/routes/api/store_usage.py
Samir Boulahtit 4aa6f76e46
Some checks failed
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / ruff (push) Successful in 10s
refactor(arch): move auth schemas to tenancy module and add cross-module service methods
Move all auth schemas (UserContext, UserLogin, LoginResponse, etc.) from
legacy models/schema/auth.py to app/modules/tenancy/schemas/auth.py per
MOD-019. Update 84 import sites across 14 modules. Legacy file now
re-exports for backwards compatibility.

Add missing tenancy service methods for cross-module consumers:
- merchant_service.get_merchant_by_owner_id()
- merchant_service.get_merchant_count_for_owner()
- admin_service.get_user_by_id() (public, was private-only)
- platform_service.get_active_store_count()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 23:57:04 +01:00

184 lines
5.2 KiB
Python

# app/modules/billing/routes/api/store_usage.py
"""
Store usage and limits API endpoints.
Provides endpoints for:
- Current usage vs limits
- Upgrade recommendations
- Approaching limit warnings
Migrated from app/api/v1/store/usage.py to billing module.
"""
import logging
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.api.deps import get_current_store_api, require_module_access
from app.core.database import get_db
from app.modules.billing.services.usage_service import usage_service
from app.modules.enums import FrontendType
from app.modules.tenancy.schemas.auth import UserContext
store_usage_router = APIRouter(
prefix="/usage",
dependencies=[Depends(require_module_access("billing", FrontendType.STORE))],
)
logger = logging.getLogger(__name__)
# ============================================================================
# Response Schemas
# ============================================================================
class UsageMetric(BaseModel):
"""Single usage metric."""
name: str
current: int
limit: int | None # None = unlimited
percentage: float # 0-100, or 0 if unlimited
is_unlimited: bool
is_at_limit: bool
is_approaching_limit: bool # >= 80%
class TierInfo(BaseModel):
"""Current tier information."""
code: str
name: str
price_monthly_cents: int
is_highest_tier: bool
class UpgradeTierInfo(BaseModel):
"""Next tier upgrade information."""
code: str
name: str
price_monthly_cents: int
price_increase_cents: int
benefits: list[str]
class UsageResponse(BaseModel):
"""Full usage response with limits and upgrade info."""
tier: TierInfo
usage: list[UsageMetric]
has_limits_approaching: bool
has_limits_reached: bool
upgrade_available: bool
upgrade_tier: UpgradeTierInfo | None = None
upgrade_reasons: list[str]
class LimitCheckResponse(BaseModel):
"""Response for checking a specific limit."""
limit_type: str
can_proceed: bool
current: int
limit: int | None
percentage: float
message: str | None = None
upgrade_tier_code: str | None = None
upgrade_tier_name: str | None = None
# ============================================================================
# Endpoints
# ============================================================================
@store_usage_router.get("", response_model=UsageResponse)
def get_usage(
current_user: UserContext = Depends(get_current_store_api),
db: Session = Depends(get_db),
):
"""
Get current usage, limits, and upgrade recommendations.
Returns comprehensive usage info for displaying in dashboard
and determining when to show upgrade prompts.
"""
store_id = current_user.token_store_id
# Get usage data from service
usage_data = usage_service.get_store_usage(db, store_id)
# Convert to response
return UsageResponse(
tier=TierInfo(
code=usage_data.tier.code,
name=usage_data.tier.name,
price_monthly_cents=usage_data.tier.price_monthly_cents,
is_highest_tier=usage_data.tier.is_highest_tier,
),
usage=[
UsageMetric(
name=m.name,
current=m.current,
limit=m.limit,
percentage=m.percentage,
is_unlimited=m.is_unlimited,
is_at_limit=m.is_at_limit,
is_approaching_limit=m.is_approaching_limit,
)
for m in usage_data.usage
],
has_limits_approaching=usage_data.has_limits_approaching,
has_limits_reached=usage_data.has_limits_reached,
upgrade_available=usage_data.upgrade_available,
upgrade_tier=(
UpgradeTierInfo(
code=usage_data.upgrade_tier.code,
name=usage_data.upgrade_tier.name,
price_monthly_cents=usage_data.upgrade_tier.price_monthly_cents,
price_increase_cents=usage_data.upgrade_tier.price_increase_cents,
benefits=usage_data.upgrade_tier.benefits,
)
if usage_data.upgrade_tier
else None
),
upgrade_reasons=usage_data.upgrade_reasons,
)
@store_usage_router.get("/check/{limit_type}", response_model=LimitCheckResponse)
def check_limit(
limit_type: str,
current_user: UserContext = Depends(get_current_store_api),
db: Session = Depends(get_db),
):
"""
Check a specific limit before performing an action.
Use this before creating orders, products, or inviting team members.
Args:
limit_type: One of "orders", "products", "team_members"
Returns:
Whether the action can proceed and upgrade info if not
"""
store_id = current_user.token_store_id
# Check limit using service
check_data = usage_service.check_limit(db, store_id, limit_type)
return LimitCheckResponse(
limit_type=check_data.limit_type,
can_proceed=check_data.can_proceed,
current=check_data.current,
limit=check_data.limit,
percentage=check_data.percentage,
message=check_data.message,
upgrade_tier_code=check_data.upgrade_tier_code,
upgrade_tier_name=check_data.upgrade_tier_name,
)