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

@@ -80,6 +80,9 @@ from app.modules.tenancy.schemas.merchant import (
MerchantPortalProfileUpdate,
MerchantPortalStoreListResponse,
MerchantResponse,
MerchantStoreCreate,
MerchantStoreDetailResponse,
MerchantStoreUpdate,
MerchantSummary,
MerchantTransferOwnership,
MerchantTransferOwnershipResponse,
@@ -163,6 +166,9 @@ __all__ = [
"MerchantPortalProfileUpdate",
"MerchantPortalStoreListResponse",
"MerchantResponse",
"MerchantStoreCreate",
"MerchantStoreDetailResponse",
"MerchantStoreUpdate",
"MerchantSummary",
"MerchantTransferOwnership",
"MerchantTransferOwnershipResponse",

View File

@@ -261,3 +261,73 @@ class MerchantPortalStoreListResponse(BaseModel):
total: int
skip: int
limit: int
can_create_store: bool = True
class MerchantStoreCreate(BaseModel):
"""Store creation from the merchant portal.
Subset of admin StoreCreate — excludes admin-only fields."""
name: str = Field(..., min_length=2, max_length=255, description="Store name")
store_code: str = Field(
..., min_length=2, max_length=50, description="Unique store code"
)
subdomain: str = Field(
..., min_length=2, max_length=100, description="Store subdomain"
)
description: str | None = Field(None, description="Store description")
platform_ids: list[int] = Field(
default_factory=list, description="Platform IDs to assign store to"
)
@field_validator("subdomain")
@classmethod
def validate_subdomain(cls, v):
"""Validate subdomain format."""
import re
v = v.lower().strip()
if not re.match(r"^[a-z0-9][a-z0-9-]*[a-z0-9]$", v) and len(v) > 1:
raise ValueError(
"Subdomain must contain only lowercase letters, numbers, and hyphens"
)
return v
@field_validator("store_code")
@classmethod
def normalize_store_code(cls, v):
"""Normalize store code to uppercase."""
return v.upper().strip()
class MerchantStoreDetailResponse(BaseModel):
"""Store detail for the merchant portal."""
id: int
store_code: str
subdomain: str
name: str
description: str | None = None
is_active: bool
is_verified: bool
contact_email: str | None = None
contact_phone: str | None = None
website: str | None = None
business_address: str | None = None
tax_number: str | None = None
default_language: str | None = None
created_at: str | None = None
platforms: list[dict] = Field(default_factory=list)
class MerchantStoreUpdate(BaseModel):
"""Store update from the merchant portal.
Only merchant-allowed fields."""
name: str | None = Field(None, min_length=2, max_length=255)
description: str | None = None
contact_email: EmailStr | None = None
contact_phone: str | None = None
website: str | None = None
business_address: str | None = None
tax_number: str | None = None