feat: multi-module improvements across merchant, store, i18n, and customer systems
All checks were successful
CI / ruff (push) Successful in 12s
CI / pytest (push) Successful in 50m57s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Successful in 40s
CI / deploy (push) Successful in 51s

- Fix platform-grouped merchant sidebar menu with core items at root level
- Add merchant store management (detail page, create store, team page)
- Fix store settings 500 error by removing dead stripe/API tab
- Move onboarding translations to module-owned locale files
- Fix onboarding banner i18n with server-side rendering + context inheritance
- Refactor login language selectors to use languageSelector() function (LANG-002)
- Move HTTPException handling to global exception handler in merchant routes (API-003)
- Add language selector to all login pages and portal headers
- Fix customer module: drop order stats from customer model, add to orders module
- Fix admin menu config visibility for super admin platform context
- Fix storefront auth and layout issues
- Add missing i18n translations for onboarding steps (en/fr/de/lb)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 23:48:25 +01:00
parent f141cc4e6a
commit a77a8a3a98
113 changed files with 3741 additions and 2923 deletions

View File

@@ -263,34 +263,49 @@ class TestMerchantMenuModuleGating:
def test_loyalty_appears_when_module_enabled(
self, client, db, menu_auth, menu_merchant, menu_subscription,
menu_loyalty_module,
menu_loyalty_module, menu_platform,
):
"""Loyalty section appears when loyalty module is enabled on subscribed platform."""
"""Loyalty items appear under platform section when module is enabled."""
response = client.get(BASE, headers=menu_auth)
assert response.status_code == 200
data = response.json()
# Loyalty now appears under a platform-{code} section
platform_section_id = f"platform-{menu_platform.code}"
section_ids = {s["id"] for s in data["sections"]}
assert "loyalty" in section_ids
assert platform_section_id in section_ids
# Check loyalty item exists in that platform section
platform_section = next(
s for s in data["sections"] if s["id"] == platform_section_id
)
item_ids = {i["id"] for i in platform_section["items"]}
assert "loyalty-overview" in item_ids
def test_loyalty_hidden_when_module_not_enabled(
self, client, db, menu_auth, menu_merchant, menu_subscription,
menu_platform,
):
"""Loyalty section is hidden when loyalty module is NOT enabled."""
"""No platform section when no non-core modules are enabled."""
response = client.get(BASE, headers=menu_auth)
assert response.status_code == 200
data = response.json()
platform_section_id = f"platform-{menu_platform.code}"
section_ids = {s["id"] for s in data["sections"]}
assert "loyalty" not in section_ids
assert platform_section_id not in section_ids
def test_loyalty_item_has_correct_route(
self, client, db, menu_auth, menu_merchant, menu_subscription,
menu_loyalty_module,
menu_loyalty_module, menu_platform,
):
"""Loyalty overview item has the correct URL."""
response = client.get(BASE, headers=menu_auth)
data = response.json()
loyalty = next(s for s in data["sections"] if s["id"] == "loyalty")
overview = next(i for i in loyalty["items"] if i["id"] == "loyalty-overview")
platform_section_id = f"platform-{menu_platform.code}"
platform_section = next(
s for s in data["sections"] if s["id"] == platform_section_id
)
overview = next(
i for i in platform_section["items"] if i["id"] == "loyalty-overview"
)
assert overview["url"] == "/merchants/loyalty/overview"
@@ -352,7 +367,8 @@ class TestMerchantMenuSubscriptionStatus:
response = client.get(BASE, headers=menu_auth)
assert response.status_code == 200
section_ids = {s["id"] for s in response.json()["sections"]}
assert "loyalty" in section_ids
platform_section_id = f"platform-{menu_platform.code}"
assert platform_section_id in section_ids
def test_expired_subscription_hides_non_core_modules(
self, client, db, menu_auth, menu_merchant, menu_platform, menu_tier,
@@ -374,8 +390,9 @@ class TestMerchantMenuSubscriptionStatus:
response = client.get(BASE, headers=menu_auth)
assert response.status_code == 200
section_ids = {s["id"] for s in response.json()["sections"]}
# Loyalty should NOT appear because subscription is expired
assert "loyalty" not in section_ids
platform_section_id = f"platform-{menu_platform.code}"
# Platform section should NOT appear because subscription is expired
assert platform_section_id not in section_ids
# Core sections always appear
assert "main" in section_ids
assert "billing" in section_ids
@@ -468,9 +485,20 @@ class TestMerchantMenuMultiPlatform:
response = client.get(BASE, headers=menu_auth)
assert response.status_code == 200
section_ids = {s["id"] for s in response.json()["sections"]}
# Loyalty enabled on Platform A should appear in the union
assert "loyalty" in section_ids
data = response.json()
section_ids = {s["id"] for s in data["sections"]}
# Loyalty enabled on Platform A appears under platform-a's section
platform_a_section_id = f"platform-{platform_a.code}"
assert platform_a_section_id in section_ids
# Platform B has no non-core modules, so no section
platform_b_section_id = f"platform-{platform_b.code}"
assert platform_b_section_id not in section_ids
# Check loyalty item exists in Platform A section
pa_section = next(
s for s in data["sections"] if s["id"] == platform_a_section_id
)
item_ids = {i["id"] for i in pa_section["items"]}
assert "loyalty-overview" in item_ids
# Core sections always present
assert "main" in section_ids
assert "billing" in section_ids