feat: complete dynamic menu system across all frontends
All checks were successful
All checks were successful
- Add "Merchant Frontend" tab to admin menu-config page - Merchant render endpoint now respects AdminMenuConfig visibility via get_merchant_primary_platform_id() platform resolution - New store menu render endpoint (GET /store/core/menu/render/store) with platform-scoped visibility and store_code interpolation - Store sidebar migrated from hardcoded Jinja2 macros to dynamic Alpine.js x-for rendering with loading skeleton and fallback - Store init-alpine.js: add loadMenuConfig(), expandSectionForCurrentPage() - Include store page route fixes, login template updates, and tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -325,6 +325,106 @@ class MenuService:
|
||||
|
||||
return all_enabled
|
||||
|
||||
def get_merchant_primary_platform_id(
|
||||
self,
|
||||
db: Session,
|
||||
merchant_id: int,
|
||||
) -> int | None:
|
||||
"""
|
||||
Get the primary platform ID for a merchant's visibility config.
|
||||
|
||||
Resolution order:
|
||||
1. Platform from the store marked is_primary in StorePlatform
|
||||
2. First active subscription's platform (fallback)
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
merchant_id: Merchant ID
|
||||
|
||||
Returns:
|
||||
Platform ID or None if no active subscriptions
|
||||
"""
|
||||
from app.modules.billing.models.merchant_subscription import (
|
||||
MerchantSubscription,
|
||||
)
|
||||
from app.modules.billing.models.subscription import SubscriptionStatus
|
||||
from app.modules.tenancy.models import Store
|
||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||
|
||||
active_statuses = [
|
||||
SubscriptionStatus.TRIAL.value,
|
||||
SubscriptionStatus.ACTIVE.value,
|
||||
SubscriptionStatus.PAST_DUE.value,
|
||||
SubscriptionStatus.CANCELLED.value,
|
||||
]
|
||||
|
||||
# Try to find the primary store's platform
|
||||
primary_platform_id = (
|
||||
db.query(StorePlatform.platform_id)
|
||||
.join(Store, Store.id == StorePlatform.store_id)
|
||||
.join(
|
||||
MerchantSubscription,
|
||||
(MerchantSubscription.platform_id == StorePlatform.platform_id)
|
||||
& (MerchantSubscription.merchant_id == merchant_id),
|
||||
)
|
||||
.filter(
|
||||
Store.merchant_id == merchant_id,
|
||||
Store.is_active == True, # noqa: E712
|
||||
StorePlatform.is_primary == True, # noqa: E712
|
||||
StorePlatform.is_active == True, # noqa: E712
|
||||
MerchantSubscription.status.in_(active_statuses),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if primary_platform_id:
|
||||
return primary_platform_id[0]
|
||||
|
||||
# Fallback: first active subscription's platform
|
||||
first_sub = (
|
||||
db.query(MerchantSubscription.platform_id)
|
||||
.filter(
|
||||
MerchantSubscription.merchant_id == merchant_id,
|
||||
MerchantSubscription.status.in_(active_statuses),
|
||||
)
|
||||
.order_by(MerchantSubscription.id)
|
||||
.first()
|
||||
)
|
||||
|
||||
return first_sub[0] if first_sub else None
|
||||
|
||||
def get_store_primary_platform_id(
|
||||
self,
|
||||
db: Session,
|
||||
store_id: int,
|
||||
) -> int | None:
|
||||
"""
|
||||
Get the primary platform ID for a store's menu visibility config.
|
||||
|
||||
Prefers the active StorePlatform marked is_primary, falls back to
|
||||
the first active StorePlatform by ID.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
store_id: Store ID
|
||||
|
||||
Returns:
|
||||
Platform ID or None if no active store-platform link
|
||||
"""
|
||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||
|
||||
sp = (
|
||||
db.query(StorePlatform.platform_id)
|
||||
.filter(
|
||||
StorePlatform.store_id == store_id,
|
||||
StorePlatform.is_active == True, # noqa: E712
|
||||
)
|
||||
.order_by(StorePlatform.is_primary.desc(), StorePlatform.id)
|
||||
.first()
|
||||
)
|
||||
|
||||
return sp[0] if sp else None
|
||||
|
||||
def get_merchant_for_menu(
|
||||
self,
|
||||
db: Session,
|
||||
|
||||
Reference in New Issue
Block a user