Files
orion/app/modules/loyalty/services/loyalty_features.py
Samir Boulahtit a8b29750a5
Some checks failed
CI / ruff (push) Successful in 9s
CI / pytest (push) Failing after 37m24s
CI / validate (push) Failing after 22s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
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>
2026-02-18 18:59:24 +01:00

185 lines
6.4 KiB
Python

# 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",
]