refactor(arch): eliminate all cross-module model imports in service layer
Some checks failed
Some checks failed
Enforce MOD-025/MOD-026 rules: zero top-level cross-module model imports remain in any service file. All 66 files migrated using deferred import patterns (method-body, _get_model() helpers, instance-cached self._Model) and new cross-module service methods in tenancy. Documentation updated with Pattern 6 (deferred imports), migration plan marked complete, and violations status reflects 84→0 service-layer violations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import logging
|
||||
from math import ceil
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
|
||||
from app.exceptions import (
|
||||
BusinessLogicException,
|
||||
@@ -27,7 +27,6 @@ from app.modules.billing.models import (
|
||||
SubscriptionStatus,
|
||||
SubscriptionTier,
|
||||
)
|
||||
from app.modules.tenancy.models import Merchant
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -143,8 +142,9 @@ class AdminSubscriptionService:
|
||||
) -> dict:
|
||||
"""List merchant subscriptions with filtering and pagination."""
|
||||
query = (
|
||||
db.query(MerchantSubscription, Merchant)
|
||||
.join(Merchant, MerchantSubscription.merchant_id == Merchant.id)
|
||||
db.query(MerchantSubscription)
|
||||
.join(MerchantSubscription.merchant)
|
||||
.options(joinedload(MerchantSubscription.merchant))
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
@@ -155,20 +155,35 @@ class AdminSubscriptionService:
|
||||
SubscriptionTier, MerchantSubscription.tier_id == SubscriptionTier.id
|
||||
).filter(SubscriptionTier.code == tier)
|
||||
if search:
|
||||
query = query.filter(Merchant.name.ilike(f"%{search}%"))
|
||||
from app.modules.tenancy.services.merchant_service import merchant_service
|
||||
|
||||
merchants, _ = merchant_service.get_merchants(db, search=search, limit=10000)
|
||||
merchant_ids = [m.id for m in merchants]
|
||||
if not merchant_ids:
|
||||
return {
|
||||
"results": [],
|
||||
"total": 0,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"pages": 0,
|
||||
}
|
||||
query = query.filter(MerchantSubscription.merchant_id.in_(merchant_ids))
|
||||
|
||||
# Count total
|
||||
total = query.count()
|
||||
|
||||
# Paginate
|
||||
offset = (page - 1) * per_page
|
||||
results = (
|
||||
subs = (
|
||||
query.order_by(MerchantSubscription.created_at.desc())
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Return (sub, merchant) tuples for backward compatibility with callers
|
||||
results = [(sub, sub.merchant) for sub in subs]
|
||||
|
||||
return {
|
||||
"results": results,
|
||||
"total": total,
|
||||
@@ -181,9 +196,9 @@ class AdminSubscriptionService:
|
||||
self, db: Session, merchant_id: int, platform_id: int
|
||||
) -> tuple:
|
||||
"""Get subscription for a specific merchant on a platform."""
|
||||
result = (
|
||||
db.query(MerchantSubscription, Merchant)
|
||||
.join(Merchant, MerchantSubscription.merchant_id == Merchant.id)
|
||||
sub = (
|
||||
db.query(MerchantSubscription)
|
||||
.options(joinedload(MerchantSubscription.merchant))
|
||||
.filter(
|
||||
MerchantSubscription.merchant_id == merchant_id,
|
||||
MerchantSubscription.platform_id == platform_id,
|
||||
@@ -191,13 +206,13 @@ class AdminSubscriptionService:
|
||||
.first()
|
||||
)
|
||||
|
||||
if not result:
|
||||
if not sub:
|
||||
raise ResourceNotFoundException(
|
||||
"Subscription",
|
||||
f"merchant_id={merchant_id}, platform_id={platform_id}",
|
||||
)
|
||||
|
||||
return result
|
||||
return sub, sub.merchant
|
||||
|
||||
def update_subscription(
|
||||
self, db: Session, merchant_id: int, platform_id: int, update_data: dict
|
||||
@@ -242,10 +257,7 @@ class AdminSubscriptionService:
|
||||
status: str | None = None,
|
||||
) -> dict:
|
||||
"""List billing history across all merchants."""
|
||||
query = (
|
||||
db.query(BillingHistory, Merchant)
|
||||
.join(Merchant, BillingHistory.merchant_id == Merchant.id)
|
||||
)
|
||||
query = db.query(BillingHistory)
|
||||
|
||||
if merchant_id:
|
||||
query = query.filter(BillingHistory.merchant_id == merchant_id)
|
||||
@@ -255,13 +267,29 @@ class AdminSubscriptionService:
|
||||
total = query.count()
|
||||
|
||||
offset = (page - 1) * per_page
|
||||
results = (
|
||||
invoices = (
|
||||
query.order_by(BillingHistory.invoice_date.desc())
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Batch-fetch merchant names for display
|
||||
from app.modules.tenancy.services.merchant_service import merchant_service
|
||||
|
||||
merchant_ids = {inv.merchant_id for inv in invoices if inv.merchant_id}
|
||||
merchants_map = {}
|
||||
for mid in merchant_ids:
|
||||
m = merchant_service.get_merchant_by_id_optional(db, mid)
|
||||
if m:
|
||||
merchants_map[mid] = m
|
||||
|
||||
# Return (invoice, merchant) tuples for backward compatibility
|
||||
results = [
|
||||
(inv, merchants_map.get(inv.merchant_id))
|
||||
for inv in invoices
|
||||
]
|
||||
|
||||
return {
|
||||
"results": results,
|
||||
"total": total,
|
||||
@@ -276,16 +304,20 @@ class AdminSubscriptionService:
|
||||
|
||||
def get_platform_names_map(self, db: Session) -> dict[int, str]:
|
||||
"""Get mapping of platform_id -> platform_name."""
|
||||
from app.modules.tenancy.models import Platform
|
||||
from app.modules.tenancy.services.platform_service import platform_service
|
||||
|
||||
return {p.id: p.name for p in db.query(Platform).all()}
|
||||
platforms = platform_service.list_platforms(db, include_inactive=True)
|
||||
return {p.id: p.name for p in platforms}
|
||||
|
||||
def get_platform_name(self, db: Session, platform_id: int) -> str | None:
|
||||
"""Get platform name by ID."""
|
||||
from app.modules.tenancy.models import Platform
|
||||
from app.modules.tenancy.services.platform_service import platform_service
|
||||
|
||||
p = db.query(Platform).filter(Platform.id == platform_id).first()
|
||||
return p.name if p else None
|
||||
try:
|
||||
p = platform_service.get_platform_by_id(db, platform_id)
|
||||
return p.name
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# Merchant Subscriptions with Usage
|
||||
@@ -359,9 +391,9 @@ class AdminSubscriptionService:
|
||||
Convenience method for admin store detail page. Resolves
|
||||
store -> merchant -> all platform subscriptions.
|
||||
"""
|
||||
from app.modules.tenancy.models import Store
|
||||
from app.modules.tenancy.services.store_service import store_service
|
||||
|
||||
store = db.query(Store).filter(Store.id == store_id).first()
|
||||
store = store_service.get_store_by_id_optional(db, store_id)
|
||||
if not store or not store.merchant_id:
|
||||
raise ResourceNotFoundException("Store", str(store_id))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user