feat: loyalty feature provider, admin data fixes, storefront mobile menu
- Add LoyaltyFeatureProvider with 11 BINARY/MERCHANT features for billing feature gating, wired into loyalty module definition - Fix subscription-tiers admin page showing 0 features by populating feature_codes from tier relationship in all admin tier endpoints - Fix merchants admin page showing 0 stores and N/A owner by adding store_count and owner_email to MerchantResponse and eager-loading owner - Add "no tiers" warning with link in subscription creation modal when platform has no configured tiers - Add missing mobile menu panel to storefront base template so hamburger toggle actually shows navigation links Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,13 @@ def _get_storefront_router():
|
||||
return storefront_router
|
||||
|
||||
|
||||
def _get_feature_provider():
|
||||
"""Lazy import of feature provider to avoid circular imports."""
|
||||
from app.modules.loyalty.services.loyalty_features import loyalty_feature_provider
|
||||
|
||||
return loyalty_feature_provider
|
||||
|
||||
|
||||
# Loyalty module definition
|
||||
loyalty_module = ModuleDefinition(
|
||||
code="loyalty",
|
||||
@@ -213,6 +220,8 @@ loyalty_module = ModuleDefinition(
|
||||
options={"queue": "scheduled"},
|
||||
),
|
||||
],
|
||||
# Feature provider for billing feature gating
|
||||
feature_provider=_get_feature_provider,
|
||||
)
|
||||
|
||||
|
||||
|
||||
184
app/modules/loyalty/services/loyalty_features.py
Normal file
184
app/modules/loyalty/services/loyalty_features.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# app/modules/loyalty/services/loyalty_features.py
|
||||
"""
|
||||
Loyalty feature provider for the billing feature system.
|
||||
|
||||
Declares loyalty-related billable features (stamps, points, cards, wallets, etc.)
|
||||
for feature gating.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.modules.contracts.features import (
|
||||
FeatureDeclaration,
|
||||
FeatureScope,
|
||||
FeatureType,
|
||||
FeatureUsage,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoyaltyFeatureProvider:
|
||||
"""Feature provider for the loyalty module.
|
||||
|
||||
Declares:
|
||||
- loyalty_stamps: stamp-based loyalty programs
|
||||
- loyalty_points: points-based loyalty programs
|
||||
- loyalty_hybrid: combined stamps and points
|
||||
- loyalty_cards: customer card management
|
||||
- loyalty_enrollment: customer enrollment
|
||||
- loyalty_staff_pins: staff PIN management
|
||||
- loyalty_anti_fraud: cooldown and daily limits
|
||||
- loyalty_google_wallet: Google Wallet passes
|
||||
- loyalty_apple_wallet: Apple Wallet passes
|
||||
- loyalty_stats: dashboard statistics
|
||||
- loyalty_reports: transaction reports
|
||||
"""
|
||||
|
||||
@property
|
||||
def feature_category(self) -> str:
|
||||
return "loyalty"
|
||||
|
||||
def get_feature_declarations(self) -> list[FeatureDeclaration]:
|
||||
return [
|
||||
FeatureDeclaration(
|
||||
code="loyalty_stamps",
|
||||
name_key="loyalty.features.loyalty_stamps.name",
|
||||
description_key="loyalty.features.loyalty_stamps.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="stamp",
|
||||
display_order=10,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_points",
|
||||
name_key="loyalty.features.loyalty_points.name",
|
||||
description_key="loyalty.features.loyalty_points.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="coins",
|
||||
display_order=20,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_hybrid",
|
||||
name_key="loyalty.features.loyalty_hybrid.name",
|
||||
description_key="loyalty.features.loyalty_hybrid.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="layers",
|
||||
display_order=30,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_cards",
|
||||
name_key="loyalty.features.loyalty_cards.name",
|
||||
description_key="loyalty.features.loyalty_cards.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="credit-card",
|
||||
display_order=40,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_enrollment",
|
||||
name_key="loyalty.features.loyalty_enrollment.name",
|
||||
description_key="loyalty.features.loyalty_enrollment.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="user-plus",
|
||||
display_order=50,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_staff_pins",
|
||||
name_key="loyalty.features.loyalty_staff_pins.name",
|
||||
description_key="loyalty.features.loyalty_staff_pins.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="shield",
|
||||
display_order=60,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_anti_fraud",
|
||||
name_key="loyalty.features.loyalty_anti_fraud.name",
|
||||
description_key="loyalty.features.loyalty_anti_fraud.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="lock",
|
||||
display_order=70,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_google_wallet",
|
||||
name_key="loyalty.features.loyalty_google_wallet.name",
|
||||
description_key="loyalty.features.loyalty_google_wallet.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="smartphone",
|
||||
display_order=80,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_apple_wallet",
|
||||
name_key="loyalty.features.loyalty_apple_wallet.name",
|
||||
description_key="loyalty.features.loyalty_apple_wallet.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="smartphone",
|
||||
display_order=85,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_stats",
|
||||
name_key="loyalty.features.loyalty_stats.name",
|
||||
description_key="loyalty.features.loyalty_stats.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="bar-chart",
|
||||
display_order=90,
|
||||
),
|
||||
FeatureDeclaration(
|
||||
code="loyalty_reports",
|
||||
name_key="loyalty.features.loyalty_reports.name",
|
||||
description_key="loyalty.features.loyalty_reports.description",
|
||||
category="loyalty",
|
||||
feature_type=FeatureType.BINARY,
|
||||
scope=FeatureScope.MERCHANT,
|
||||
ui_icon="file-text",
|
||||
display_order=100,
|
||||
),
|
||||
]
|
||||
|
||||
def get_store_usage(
|
||||
self,
|
||||
db: Session,
|
||||
store_id: int,
|
||||
) -> list[FeatureUsage]:
|
||||
return []
|
||||
|
||||
def get_merchant_usage(
|
||||
self,
|
||||
db: Session,
|
||||
merchant_id: int,
|
||||
platform_id: int,
|
||||
) -> list[FeatureUsage]:
|
||||
return []
|
||||
|
||||
|
||||
# Singleton instance for module registration
|
||||
loyalty_feature_provider = LoyaltyFeatureProvider()
|
||||
|
||||
__all__ = [
|
||||
"LoyaltyFeatureProvider",
|
||||
"loyalty_feature_provider",
|
||||
]
|
||||
Reference in New Issue
Block a user