Files
orion/app/modules/loyalty/definition.py
Samir Boulahtit adc36246b8
Some checks failed
CI / ruff (push) Successful in 14s
CI / pytest (push) Failing after 2h32m45s
CI / validate (push) Successful in 30s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
feat(storefront): homepage, module gating, widget protocol, i18n fixes
Storefront homepage & module gating:
- CMS owns storefront GET / (slug="home" with 3-tier resolution)
- Catalog loses GET / (keeps /products only)
- Store root redirect (GET / → /store/dashboard or /store/login)
- Route gating: non-core modules return 404 when disabled for platform
- Seed store default homepages per platform

Widget protocol for customer dashboard:
- StorefrontDashboardCard contract in widgets.py
- Widget aggregator get_storefront_dashboard_cards()
- Orders and Loyalty module widget providers
- Dashboard template renders contributed cards (no module names)

Landing template module-agnostic:
- CTAs driven by storefront_nav (not hardcoded module names)
- Header actions check nav item IDs (not enabled_modules)
- Remove hardcoded "Add Product" sidebar button
- Remove all enabled_modules checks from storefront templates

i18n fixes:
- Title placeholder resolution ({{store_name}}) for store default pages
- Storefront nav label_keys prefixed with module code
- Add storefront.account.* keys to 6 modules (en/fr/de/lb)
- Header/footer CMS pages use get_translated_title(current_language)
- Footer labels use i18n keys instead of hardcoded English

Icon cleanup:
- Standardize on map-pin (remove location-marker alias)
- Replace all location-marker references across templates and docs

Docs:
- Storefront builder vision proposal (6 phases)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 22:53:17 +02:00

361 lines
13 KiB
Python

# app/modules/loyalty/definition.py
"""
Loyalty module definition.
Defines the loyalty module including its features, menu items,
route configurations, and scheduled tasks.
"""
from app.modules.base import (
MenuItemDefinition,
MenuSectionDefinition,
ModuleDefinition,
PermissionDefinition,
ScheduledTask,
)
from app.modules.enums import FrontendType
def _get_admin_router():
"""Lazy import of admin router to avoid circular imports."""
from app.modules.loyalty.routes.api.admin import router
return router
def _get_merchant_router():
"""Lazy import of merchant router to avoid circular imports."""
from app.modules.loyalty.routes.api.merchant import router as merchant_router
return merchant_router
def _get_store_router():
"""Lazy import of store router to avoid circular imports."""
from app.modules.loyalty.routes.api.store import router
return router
def _get_platform_router():
"""Lazy import of platform router to avoid circular imports."""
from app.modules.loyalty.routes.api.platform import platform_router
return platform_router
def _get_storefront_router():
"""Lazy import of storefront router to avoid circular imports."""
from app.modules.loyalty.routes.api.storefront import 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
def _get_widget_provider():
"""Lazy import of widget provider to avoid circular imports."""
from app.modules.loyalty.services.loyalty_widgets import loyalty_widget_provider
return loyalty_widget_provider
def _get_onboarding_provider():
"""Lazy import of onboarding provider to avoid circular imports."""
from app.modules.loyalty.services.loyalty_onboarding_service import (
loyalty_onboarding_provider,
)
return loyalty_onboarding_provider
# Loyalty module definition
loyalty_module = ModuleDefinition(
code="loyalty",
name="Loyalty Programs",
description=(
"Stamp-based and points-based loyalty programs with Google Wallet "
"and Apple Wallet integration. Includes anti-fraud features like "
"staff PINs, cooldown periods, and daily limits."
),
version="1.0.0",
requires=["customers"], # Depends on customers module for customer data
features=[
# Core features
"loyalty_stamps", # Stamp-based loyalty
"loyalty_points", # Points-based loyalty
"loyalty_hybrid", # Both stamps and points
# Card management
"loyalty_cards", # Customer card management
"loyalty_enrollment", # Customer enrollment
# Staff/fraud prevention
"loyalty_staff_pins", # Staff PIN management
"loyalty_anti_fraud", # Cooldown, daily limits
# Wallet integration
"loyalty_google_wallet", # Google Wallet passes
"loyalty_apple_wallet", # Apple Wallet passes
# Analytics
"loyalty_stats", # Dashboard statistics
"loyalty_reports", # Transaction reports
],
# Module-driven permissions
permissions=[
PermissionDefinition(
id="loyalty.view_programs",
label_key="loyalty.permissions.view_programs",
description_key="loyalty.permissions.view_programs_desc",
category="loyalty",
),
PermissionDefinition(
id="loyalty.manage_programs",
label_key="loyalty.permissions.manage_programs",
description_key="loyalty.permissions.manage_programs_desc",
category="loyalty",
),
PermissionDefinition(
id="loyalty.view_rewards",
label_key="loyalty.permissions.view_rewards",
description_key="loyalty.permissions.view_rewards_desc",
category="loyalty",
),
PermissionDefinition(
id="loyalty.manage_rewards",
label_key="loyalty.permissions.manage_rewards",
description_key="loyalty.permissions.manage_rewards_desc",
category="loyalty",
),
],
menu_items={
FrontendType.ADMIN: [
"loyalty-programs", # View all programs
"loyalty-analytics", # Platform-wide stats
"wallet-debug", # Wallet diagnostics (super admin)
],
FrontendType.STORE: [
"terminal", # Loyalty terminal
"cards", # Customer cards
"pins", # Staff PINs
"program", # Program config
"analytics", # Store analytics
],
FrontendType.MERCHANT: [
"program", # Merchant loyalty program
"cards", # Customer cards
"analytics", # Merchant loyalty analytics
"transactions", # Transaction feed
"pins", # Staff PINs
"settings", # Settings (read-only)
],
},
# New module-driven menu definitions
menus={
FrontendType.ADMIN: [
MenuSectionDefinition(
id="loyalty",
label_key="loyalty.menu.loyalty",
icon="gift",
order=55,
items=[
MenuItemDefinition(
id="loyalty-programs",
label_key="loyalty.menu.programs",
icon="gift",
route="/admin/loyalty/programs",
order=10,
),
MenuItemDefinition(
id="loyalty-analytics",
label_key="loyalty.menu.analytics",
icon="chart-bar",
route="/admin/loyalty/analytics",
order=20,
),
MenuItemDefinition(
id="wallet-debug",
label_key="loyalty.menu.wallet_debug",
icon="beaker",
route="/admin/loyalty/wallet-debug",
order=30,
is_super_admin_only=True,
),
],
),
],
FrontendType.STORE: [
MenuSectionDefinition(
id="loyalty",
label_key="loyalty.menu.loyalty_programs",
icon="gift",
order=35,
items=[
MenuItemDefinition(
id="terminal",
label_key="loyalty.menu.terminal",
icon="gift",
route="/store/{store_code}/loyalty/terminal",
order=10,
requires_permission="loyalty.view_programs",
),
MenuItemDefinition(
id="cards",
label_key="loyalty.menu.customer_cards",
icon="identification",
route="/store/{store_code}/loyalty/cards",
order=20,
requires_permission="loyalty.view_programs",
),
MenuItemDefinition(
id="pins",
label_key="loyalty.menu.staff_pins",
icon="key",
route="/store/{store_code}/loyalty/pins",
order=22,
requires_permission="loyalty.view_programs",
),
MenuItemDefinition(
id="program",
label_key="loyalty.menu.program",
icon="cog",
route="/store/{store_code}/loyalty/program",
order=25,
requires_permission="loyalty.view_programs",
),
MenuItemDefinition(
id="analytics",
label_key="loyalty.menu.analytics",
icon="chart-bar",
route="/store/{store_code}/loyalty/analytics",
order=30,
requires_permission="loyalty.view_programs",
),
],
),
],
FrontendType.MERCHANT: [
MenuSectionDefinition(
id="loyalty",
label_key="loyalty.menu.loyalty",
icon="gift",
order=60,
items=[
MenuItemDefinition(
id="program",
label_key="loyalty.menu.program",
icon="gift",
route="/merchants/loyalty/program",
order=10,
),
MenuItemDefinition(
id="cards",
label_key="loyalty.menu.customer_cards",
icon="identification",
route="/merchants/loyalty/cards",
order=15,
),
MenuItemDefinition(
id="analytics",
label_key="loyalty.menu.analytics",
icon="chart-bar",
route="/merchants/loyalty/analytics",
order=20,
),
MenuItemDefinition(
id="transactions",
label_key="loyalty.menu.transactions",
icon="clock",
route="/merchants/loyalty/transactions",
order=25,
),
MenuItemDefinition(
id="pins",
label_key="loyalty.menu.staff_pins",
icon="key",
route="/merchants/loyalty/pins",
order=30,
),
MenuItemDefinition(
id="settings",
label_key="loyalty.menu.settings",
icon="cog",
route="/merchants/loyalty/settings",
order=35,
),
],
),
],
FrontendType.STOREFRONT: [
MenuSectionDefinition(
id="account",
label_key=None,
order=10,
items=[
MenuItemDefinition(
id="loyalty",
label_key="loyalty.storefront.account.loyalty",
icon="gift",
route="account/loyalty",
order=60,
),
],
),
],
},
is_core=False, # Loyalty can be disabled
# =========================================================================
# Self-Contained Module Configuration
# =========================================================================
is_self_contained=True,
services_path="app.modules.loyalty.services",
models_path="app.modules.loyalty.models",
schemas_path="app.modules.loyalty.schemas",
exceptions_path="app.modules.loyalty.exceptions",
tasks_path="app.modules.loyalty.tasks",
migrations_path="migrations",
# =========================================================================
# Scheduled Tasks
# =========================================================================
scheduled_tasks=[
ScheduledTask(
name="loyalty.sync_wallet_passes",
task="app.modules.loyalty.tasks.wallet_sync.sync_wallet_passes",
schedule="0 * * * *", # Hourly
options={"queue": "scheduled"},
),
ScheduledTask(
name="loyalty.expire_points",
task="app.modules.loyalty.tasks.point_expiration.expire_points",
schedule="0 2 * * *", # Daily at 02:00
options={"queue": "scheduled"},
),
],
# Feature provider for billing feature gating
feature_provider=_get_feature_provider,
# Widget provider for storefront dashboard cards
widget_provider=_get_widget_provider,
# Onboarding provider for post-signup checklist
onboarding_provider=_get_onboarding_provider,
)
def get_loyalty_module_with_routers() -> ModuleDefinition:
"""
Get loyalty module with routers attached.
This function attaches the routers lazily to avoid circular imports
during module initialization.
"""
loyalty_module.admin_router = _get_admin_router()
loyalty_module.merchant_router = _get_merchant_router()
loyalty_module.store_router = _get_store_router()
loyalty_module.platform_router = _get_platform_router()
loyalty_module.storefront_router = _get_storefront_router()
return loyalty_module
__all__ = ["loyalty_module", "get_loyalty_module_with_routers"]