feat(tenancy): add merchant-level domain with store override
Merchants can now register domains (e.g., myloyaltyprogram.lu) that all their stores inherit. Individual stores can override with their own custom domain. Resolution priority: StoreDomain > MerchantDomain > subdomain. - Add MerchantDomain model, schema, service, and admin API endpoints - Add merchant domain fallback in platform and store context middleware - Add Merchant.primary_domain and Store.effective_domain properties - Add Alembic migration for merchant_domains table - Update loyalty user journey docs with subscription & domain setup flow - Add unit tests (50 passing) and integration tests (15 passing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,13 @@ class Merchant(Base, TimestampMixin):
|
||||
)
|
||||
"""All store brands operated by this merchant."""
|
||||
|
||||
merchant_domains = relationship(
|
||||
"MerchantDomain",
|
||||
back_populates="merchant",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
"""Custom domains registered at the merchant level (inherited by all stores)."""
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of the Merchant object."""
|
||||
return f"<Merchant(id={self.id}, name='{self.name}', stores={len(self.stores) if self.stores else 0})>"
|
||||
@@ -98,6 +105,14 @@ class Merchant(Base, TimestampMixin):
|
||||
"""Get the number of stores belonging to this merchant."""
|
||||
return len(self.stores) if self.stores else 0
|
||||
|
||||
@property
|
||||
def primary_domain(self) -> str | None:
|
||||
"""Get the primary active and verified merchant domain."""
|
||||
for md in self.merchant_domains:
|
||||
if md.is_primary and md.is_active and md.is_verified:
|
||||
return md.domain
|
||||
return None
|
||||
|
||||
@property
|
||||
def active_store_count(self) -> int:
|
||||
"""Get the number of active stores belonging to this merchant."""
|
||||
|
||||
Reference in New Issue
Block a user