refactor(arch): eliminate all cross-module model imports in service layer
Some checks failed
CI / ruff (push) Successful in 9s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / pytest (push) Has been cancelled

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:
2026-02-27 06:13:15 +01:00
parent e3a52f6536
commit 86e85a98b8
66 changed files with 2242 additions and 1295 deletions

View File

@@ -75,9 +75,9 @@ class ProgramService:
Looks up the store's merchant and returns the merchant's program.
"""
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:
return None
@@ -89,9 +89,9 @@ class ProgramService:
Looks up the store's merchant and returns the merchant's active program.
"""
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:
return None
@@ -140,15 +140,9 @@ class ProgramService:
StoreNotFoundException: If store not found
"""
from app.modules.tenancy.exceptions import StoreNotFoundException
from app.modules.tenancy.models import Store
from app.modules.tenancy.services.store_service import store_service
store = (
db.query(Store)
.filter(
(Store.store_code == store_code) | (Store.subdomain == store_code)
)
.first()
)
store = store_service.get_store_by_code_or_subdomain(db, store_code)
if not store:
raise StoreNotFoundException(store_code)
return store
@@ -168,9 +162,9 @@ class ProgramService:
StoreNotFoundException: If store not found
"""
from app.modules.tenancy.exceptions import StoreNotFoundException
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:
raise StoreNotFoundException(str(store_id), identifier_type="id")
return store.merchant_id
@@ -186,12 +180,10 @@ class ProgramService:
Returns:
List of active Store objects
"""
from app.modules.tenancy.models import Store
from app.modules.tenancy.services.store_service import store_service
return (
db.query(Store)
.filter(Store.merchant_id == merchant_id, Store.is_active == True)
.all()
return store_service.get_stores_by_merchant_id(
db, merchant_id, active_only=True
)
def get_program_list_stats(self, db: Session, program) -> dict:
@@ -209,9 +201,9 @@ class ProgramService:
from sqlalchemy import func
from app.modules.loyalty.models import LoyaltyCard, LoyaltyTransaction
from app.modules.tenancy.models import Merchant
from app.modules.tenancy.services.merchant_service import merchant_service
merchant = db.query(Merchant).filter(Merchant.id == program.merchant_id).first()
merchant = merchant_service.get_merchant_by_id_optional(db, program.merchant_id)
merchant_name = merchant.name if merchant else None
total_cards = (
@@ -372,18 +364,16 @@ class ProgramService:
is_active: Filter by active status
search: Search by merchant name (case-insensitive)
"""
from app.modules.tenancy.models import Merchant
query = db.query(LoyaltyProgram).join(
Merchant, LoyaltyProgram.merchant_id == Merchant.id
)
query = db.query(LoyaltyProgram)
if is_active is not None:
query = query.filter(LoyaltyProgram.is_active == is_active)
if search:
search_pattern = f"%{search}%"
query = query.filter(Merchant.name.ilike(search_pattern))
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]
query = query.filter(LoyaltyProgram.merchant_id.in_(merchant_ids))
total = query.count()
programs = query.order_by(LoyaltyProgram.created_at.desc()).offset(skip).limit(limit).all()
@@ -720,7 +710,7 @@ class ProgramService:
from sqlalchemy import func
from app.modules.loyalty.models import LoyaltyCard, LoyaltyTransaction
from app.modules.tenancy.models import Store
from app.modules.tenancy.services.store_service import store_service
program = self.get_program_by_merchant(db, merchant_id)
@@ -834,7 +824,7 @@ class ProgramService:
)
# Get all stores for this merchant for location breakdown
stores = db.query(Store).filter(Store.merchant_id == merchant_id).all()
stores = store_service.get_stores_by_merchant_id(db, merchant_id)
location_stats = []
for store in stores: