fix(billing): resolve 3 IMPORT-001 architecture violations in billing module
Replace direct imports from optional modules (catalog, orders, analytics) with provider pattern calls (stats_aggregator, feature_aggregator) and move usage_service from analytics to billing where it belongs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,26 +9,9 @@ from app.modules.analytics.services.stats_service import (
|
|||||||
stats_service,
|
stats_service,
|
||||||
StatsService,
|
StatsService,
|
||||||
)
|
)
|
||||||
from app.modules.analytics.services.usage_service import (
|
|
||||||
usage_service,
|
|
||||||
UsageService,
|
|
||||||
UsageData,
|
|
||||||
UsageMetricData,
|
|
||||||
TierInfoData,
|
|
||||||
UpgradeTierData,
|
|
||||||
LimitCheckData,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Stats service
|
# Stats service
|
||||||
"stats_service",
|
"stats_service",
|
||||||
"StatsService",
|
"StatsService",
|
||||||
# Usage service
|
|
||||||
"usage_service",
|
|
||||||
"UsageService",
|
|
||||||
"UsageData",
|
|
||||||
"UsageMetricData",
|
|
||||||
"TierInfoData",
|
|
||||||
"UpgradeTierData",
|
|
||||||
"LimitCheckData",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.api.deps import get_current_store_api, require_module_access
|
from app.api.deps import get_current_store_api, require_module_access
|
||||||
from app.core.database import get_db
|
from app.core.database import get_db
|
||||||
from app.modules.analytics.services.usage_service import usage_service
|
from app.modules.billing.services.usage_service import usage_service
|
||||||
from app.modules.enums import FrontendType
|
from app.modules.enums import FrontendType
|
||||||
from models.schema.auth import UserContext
|
from models.schema.auth import UserContext
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ from app.modules.billing.services.platform_pricing_service import (
|
|||||||
PlatformPricingService,
|
PlatformPricingService,
|
||||||
platform_pricing_service,
|
platform_pricing_service,
|
||||||
)
|
)
|
||||||
|
from app.modules.billing.services.usage_service import (
|
||||||
|
UsageService,
|
||||||
|
usage_service,
|
||||||
|
UsageData,
|
||||||
|
UsageMetricData,
|
||||||
|
TierInfoData,
|
||||||
|
UpgradeTierData,
|
||||||
|
LimitCheckData,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SubscriptionService",
|
"SubscriptionService",
|
||||||
@@ -63,4 +72,11 @@ __all__ = [
|
|||||||
"capacity_forecast_service",
|
"capacity_forecast_service",
|
||||||
"PlatformPricingService",
|
"PlatformPricingService",
|
||||||
"platform_pricing_service",
|
"platform_pricing_service",
|
||||||
|
"UsageService",
|
||||||
|
"usage_service",
|
||||||
|
"UsageData",
|
||||||
|
"UsageMetricData",
|
||||||
|
"TierInfoData",
|
||||||
|
"UpgradeTierData",
|
||||||
|
"LimitCheckData",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ from decimal import Decimal
|
|||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.modules.catalog.models import Product
|
|
||||||
from app.modules.billing.models import (
|
from app.modules.billing.models import (
|
||||||
CapacitySnapshot,
|
CapacitySnapshot,
|
||||||
MerchantSubscription,
|
MerchantSubscription,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
)
|
)
|
||||||
from app.modules.tenancy.models import Store, StoreUser
|
from app.modules.contracts.metrics import MetricsContext
|
||||||
|
from app.modules.core.services.stats_aggregator import stats_aggregator
|
||||||
|
from app.modules.tenancy.models import Platform, Store, StoreUser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -87,8 +88,17 @@ class CapacityForecastService:
|
|||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
# Resource metrics
|
# Resource metrics via provider pattern (avoids direct catalog/orders imports)
|
||||||
total_products = db.query(func.count(Product.id)).scalar() or 0
|
start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
platform = db.query(Platform).first()
|
||||||
|
platform_id = platform.id if platform else 1
|
||||||
|
|
||||||
|
stats = stats_aggregator.get_admin_stats_flat(
|
||||||
|
db, platform_id,
|
||||||
|
context=MetricsContext(date_from=start_of_month),
|
||||||
|
)
|
||||||
|
|
||||||
|
total_products = stats.get("catalog.total_products", 0)
|
||||||
total_team = (
|
total_team = (
|
||||||
db.query(func.count(StoreUser.id))
|
db.query(func.count(StoreUser.id))
|
||||||
.filter(StoreUser.is_active == True) # noqa: E712
|
.filter(StoreUser.is_active == True) # noqa: E712
|
||||||
@@ -96,15 +106,8 @@ class CapacityForecastService:
|
|||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
# Orders this month
|
# Orders this month (from stats aggregator)
|
||||||
start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
total_orders = stats.get("orders.in_period", 0)
|
||||||
from app.modules.orders.models import Order
|
|
||||||
|
|
||||||
total_orders = (
|
|
||||||
db.query(func.count(Order.id))
|
|
||||||
.filter(Order.created_at >= start_of_month)
|
|
||||||
.scalar() or 0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Storage metrics
|
# Storage metrics
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# app/modules/analytics/services/usage_service.py
|
# app/modules/billing/services/usage_service.py
|
||||||
"""
|
"""
|
||||||
Usage and limits service.
|
Usage and limits service.
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ from dataclasses import dataclass
|
|||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.modules.catalog.models import Product
|
|
||||||
from app.modules.billing.models import MerchantSubscription, SubscriptionTier
|
from app.modules.billing.models import MerchantSubscription, SubscriptionTier
|
||||||
|
from app.modules.billing.services.feature_aggregator import feature_aggregator
|
||||||
from app.modules.tenancy.models import StoreUser
|
from app.modules.tenancy.models import StoreUser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -213,13 +213,10 @@ class UsageService:
|
|||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
def _get_product_count(self, db: Session, store_id: int) -> int:
|
def _get_product_count(self, db: Session, store_id: int) -> int:
|
||||||
"""Get product count for store."""
|
"""Get product count for store via feature aggregator."""
|
||||||
return (
|
usage = feature_aggregator.get_store_usage(db, store_id)
|
||||||
db.query(func.count(Product.id))
|
products = usage.get("products_limit")
|
||||||
.filter(Product.store_id == store_id)
|
return products.current_count if products else 0
|
||||||
.scalar()
|
|
||||||
or 0
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_team_member_count(self, db: Session, store_id: int) -> int:
|
def _get_team_member_count(self, db: Session, store_id: int) -> int:
|
||||||
"""Get active team member count for store."""
|
"""Get active team member count for store."""
|
||||||
@@ -271,23 +268,10 @@ class UsageService:
|
|||||||
def _get_orders_this_period(
|
def _get_orders_this_period(
|
||||||
self, db: Session, store_id: int, subscription: MerchantSubscription | None
|
self, db: Session, store_id: int, subscription: MerchantSubscription | None
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Get order count for the current billing period."""
|
"""Get order count for the current billing period via feature aggregator."""
|
||||||
from app.modules.orders.models import Order
|
usage = feature_aggregator.get_store_usage(db, store_id)
|
||||||
|
orders = usage.get("orders_per_month")
|
||||||
period_start = subscription.period_start if subscription else None
|
return orders.current_count if orders else 0
|
||||||
if not period_start:
|
|
||||||
from datetime import datetime, UTC
|
|
||||||
period_start = datetime.now(UTC).replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
||||||
|
|
||||||
return (
|
|
||||||
db.query(func.count(Order.id))
|
|
||||||
.filter(
|
|
||||||
Order.store_id == store_id,
|
|
||||||
Order.created_at >= period_start,
|
|
||||||
)
|
|
||||||
.scalar()
|
|
||||||
or 0
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_next_tier(
|
def _get_next_tier(
|
||||||
self, db: Session, current_tier: SubscriptionTier | None
|
self, db: Session, current_tier: SubscriptionTier | None
|
||||||
Reference in New Issue
Block a user