diff --git a/.architecture-rules/frontend.yaml b/.architecture-rules/frontend.yaml
index b33dad69..6c9782f8 100644
--- a/.architecture-rules/frontend.yaml
+++ b/.architecture-rules/frontend.yaml
@@ -77,6 +77,60 @@ javascript_rules:
file_pattern: "static/**/js/**/*.js"
check: "async_error_handling"
+ - id: "JS-013"
+ name: "Components overriding init() must call parent init"
+ severity: "error"
+ description: |
+ When an Alpine.js component spreads ...data() and defines its own init() method,
+ it MUST call the parent init() first. The parent init() sets critical properties
+ like vendorCode (from URL), currentUser, and theme preference.
+
+ Without calling parent init(), properties like vendorCode will be null, causing
+ API calls like `/vendor/${this.vendorCode}/settings` to fail with
+ "Endpoint not found: /api/v1/vendor/null/settings".
+
+ WRONG (parent init never called):
+ function vendorSettings() {
+ return {
+ ...data(),
+ async init() {
+ await this.loadSettings(); // this.vendorCode is null!
+ }
+ };
+ }
+
+ RIGHT (call parent init first):
+ function vendorSettings() {
+ return {
+ ...data(),
+ async init() {
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
+ await this.loadSettings(); // this.vendorCode is now set
+ }
+ };
+ }
+
+ This pattern is required for ALL page-specific JavaScript files that:
+ 1. Use ...data() to inherit base layout functionality
+ 2. Define their own init() method
+ pattern:
+ file_pattern: "static/vendor/js/**/*.js"
+ check: "parent_init_call"
+ required_when:
+ - "contains: '...data()'"
+ - "contains: 'async init()'"
+ required_pattern:
+ - "data\\(\\)\\.init"
+ - "parentInit"
+ exceptions:
+ - "init-alpine.js"
+ - "login.js"
+
- id: "JS-007"
name: "Set loading state before async operations"
severity: "warning"
diff --git a/alembic/versions/s7a8b9c0d1e2_add_storefront_locale_to_vendors.py b/alembic/versions/s7a8b9c0d1e2_add_storefront_locale_to_vendors.py
new file mode 100644
index 00000000..b11cb9a0
--- /dev/null
+++ b/alembic/versions/s7a8b9c0d1e2_add_storefront_locale_to_vendors.py
@@ -0,0 +1,40 @@
+# alembic/versions/s7a8b9c0d1e2_add_storefront_locale_to_vendors.py
+"""Add storefront_locale to vendors for currency formatting.
+
+Revision ID: s7a8b9c0d1e2
+Revises: r6f7a8b9c0d1
+Create Date: 2026-01-02 20:00:00.000000
+
+This migration adds a nullable storefront_locale field to vendors.
+NULL means the vendor inherits from platform defaults.
+Examples: 'fr-LU', 'de-DE', 'en-GB'
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "s7a8b9c0d1e2"
+down_revision = "r6f7a8b9c0d1"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ """Add storefront_locale column to vendors table."""
+ # Nullable - NULL means "inherit from platform default"
+ op.add_column(
+ "vendors",
+ sa.Column(
+ "storefront_locale",
+ sa.String(10),
+ nullable=True,
+ comment="Currency/number formatting locale (NULL = inherit from platform)",
+ ),
+ )
+
+
+def downgrade() -> None:
+ """Remove storefront_locale column from vendors table."""
+ op.drop_column("vendors", "storefront_locale")
diff --git a/app/api/v1/shop/profile.py b/app/api/v1/shop/profile.py
index cc135dcf..32d76086 100644
--- a/app/api/v1/shop/profile.py
+++ b/app/api/v1/shop/profile.py
@@ -15,6 +15,7 @@ from app.api.deps import get_current_customer_api
from app.core.database import get_db
from app.exceptions import ValidationException
from app.services.auth_service import AuthService
+from app.services.customer_service import customer_service
from models.database.customer import Customer
from models.schema.customer import (
CustomerPasswordChange,
@@ -81,16 +82,10 @@ def update_profile(
# If email is being changed, check uniqueness within vendor
if update_data.email and update_data.email != customer.email:
- existing = (
- db.query(Customer)
- .filter(
- Customer.vendor_id == customer.vendor_id,
- Customer.email == update_data.email,
- Customer.id != customer.id,
- )
- .first()
+ existing = customer_service.get_customer_by_email(
+ db, customer.vendor_id, update_data.email
)
- if existing:
+ if existing and existing.id != customer.id:
raise ValidationException("Email already in use")
# Update only provided fields
diff --git a/app/core/config.py b/app/core/config.py
index 6e814786..a2475ba7 100644
--- a/app/core/config.py
+++ b/app/core/config.py
@@ -158,6 +158,13 @@ class Settings(BaseSettings):
email_enabled: bool = True # Set to False to disable all emails
email_debug: bool = False # Log emails instead of sending (for development)
+ # =============================================================================
+ # STOREFRONT DEFAULTS
+ # =============================================================================
+ # These can be overridden by AdminSetting in the database
+ default_storefront_locale: str = "fr-LU" # Currency/number formatting locale
+ default_currency: str = "EUR" # Default currency code
+
# =============================================================================
# DEMO/SEED DATA CONFIGURATION
# =============================================================================
diff --git a/app/routes/shop_pages.py b/app/routes/shop_pages.py
index ea5d58fe..72281576 100644
--- a/app/routes/shop_pages.py
+++ b/app/routes/shop_pages.py
@@ -39,6 +39,7 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_customer_from_cookie_or_header, get_db
from app.services.content_page_service import content_page_service
+from app.services.platform_settings_service import platform_settings_service
from models.database.customer import Customer
router = APIRouter()
@@ -47,6 +48,40 @@ templates = Jinja2Templates(directory="app/templates")
logger = logging.getLogger(__name__)
+# ============================================================================
+# HELPER: Resolve Storefront Locale
+# ============================================================================
+
+
+def get_resolved_storefront_config(db: Session, vendor) -> dict:
+ """
+ Resolve storefront locale and currency with priority:
+ 1. Vendor's storefront_locale (if set)
+ 2. Platform's default_storefront_locale (from AdminSetting)
+ 3. Environment variable (from config)
+ 4. Hardcoded fallback: 'fr-LU'
+
+ Args:
+ db: Database session
+ vendor: Vendor model instance
+
+ Returns:
+ dict with 'locale' and 'currency' keys
+ """
+ # Get platform defaults from service (handles resolution chain 2-4)
+ platform_config = platform_settings_service.get_storefront_config(db)
+
+ # Check for vendor override (step 1)
+ locale = platform_config["locale"]
+ if vendor and vendor.storefront_locale:
+ locale = vendor.storefront_locale
+
+ return {
+ "locale": locale,
+ "currency": platform_config["currency"],
+ }
+
+
# ============================================================================
# HELPER: Build Shop Template Context
# ============================================================================
@@ -133,6 +168,11 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
extra={"error": str(e), "vendor_id": vendor.id if vendor else None},
)
+ # Resolve storefront locale and currency
+ storefront_config = {"locale": "fr-LU", "currency": "EUR"} # defaults
+ if db and vendor:
+ storefront_config = get_resolved_storefront_config(db, vendor)
+
context = {
"request": request,
"vendor": vendor,
@@ -142,6 +182,8 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
"base_url": base_url,
"footer_pages": footer_pages,
"header_pages": header_pages,
+ "storefront_locale": storefront_config["locale"],
+ "storefront_currency": storefront_config["currency"],
}
# Add any extra context (user, product_id, category_slug, etc.)
@@ -157,6 +199,8 @@ def get_shop_context(request: Request, db: Session = None, **extra_context) -> d
"has_theme": theme is not None,
"access_method": access_method,
"base_url": base_url,
+ "storefront_locale": storefront_config["locale"],
+ "storefront_currency": storefront_config["currency"],
"footer_pages_count": len(footer_pages),
"header_pages_count": len(header_pages),
"extra_keys": list(extra_context.keys()) if extra_context else [],
diff --git a/app/services/platform_settings_service.py b/app/services/platform_settings_service.py
new file mode 100644
index 00000000..8f8c189b
--- /dev/null
+++ b/app/services/platform_settings_service.py
@@ -0,0 +1,176 @@
+# app/services/platform_settings_service.py
+"""
+Platform Settings Service
+
+Provides access to platform-wide settings with a resolution chain:
+1. AdminSetting from database (can be set via admin UI)
+2. Environment variables (from .env/config)
+3. Hardcoded defaults
+
+This allows admins to override defaults without code changes,
+while still supporting environment-based configuration.
+"""
+
+import logging
+from typing import Any
+
+from sqlalchemy.orm import Session
+
+from app.core.config import settings
+from models.database.admin import AdminSetting
+
+logger = logging.getLogger(__name__)
+
+
+class PlatformSettingsService:
+ """
+ Service for accessing platform-wide settings.
+
+ Resolution order:
+ 1. AdminSetting in database (highest priority)
+ 2. Environment variable via config
+ 3. Hardcoded default (lowest priority)
+ """
+
+ # Mapping of setting keys to their config attribute names and defaults
+ SETTINGS_MAP = {
+ "default_storefront_locale": {
+ "config_attr": "default_storefront_locale",
+ "default": "fr-LU",
+ "description": "Default locale for currency/number formatting (e.g., fr-LU, de-DE)",
+ "category": "storefront",
+ },
+ "default_currency": {
+ "config_attr": "default_currency",
+ "default": "EUR",
+ "description": "Default currency code for the platform",
+ "category": "storefront",
+ },
+ }
+
+ def get(self, db: Session, key: str) -> str | None:
+ """
+ Get a setting value with full resolution chain.
+
+ Args:
+ db: Database session
+ key: Setting key (e.g., 'default_storefront_locale')
+
+ Returns:
+ Setting value or None if not found
+ """
+ # 1. Check AdminSetting in database
+ admin_setting = db.query(AdminSetting).filter_by(key=key).first()
+ if admin_setting and admin_setting.value:
+ logger.debug(f"Setting '{key}' resolved from AdminSetting: {admin_setting.value}")
+ return admin_setting.value
+
+ # 2. Check environment/config
+ setting_info = self.SETTINGS_MAP.get(key)
+ if setting_info:
+ config_attr = setting_info.get("config_attr")
+ if config_attr and hasattr(settings, config_attr):
+ value = getattr(settings, config_attr)
+ logger.debug(f"Setting '{key}' resolved from config: {value}")
+ return value
+
+ # 3. Return hardcoded default
+ default = setting_info.get("default")
+ logger.debug(f"Setting '{key}' resolved from default: {default}")
+ return default
+
+ logger.warning(f"Unknown setting key: {key}")
+ return None
+
+ def get_storefront_locale(self, db: Session) -> str:
+ """Get the default storefront locale."""
+ return self.get(db, "default_storefront_locale") or "fr-LU"
+
+ def get_currency(self, db: Session) -> str:
+ """Get the default currency."""
+ return self.get(db, "default_currency") or "EUR"
+
+ def get_storefront_config(self, db: Session) -> dict[str, str]:
+ """
+ Get all storefront-related settings as a dict.
+
+ Returns:
+ Dict with 'locale' and 'currency' keys
+ """
+ return {
+ "locale": self.get_storefront_locale(db),
+ "currency": self.get_currency(db),
+ }
+
+ def set(self, db: Session, key: str, value: str, user_id: int | None = None) -> AdminSetting:
+ """
+ Set a platform setting in the database.
+
+ Args:
+ db: Database session
+ key: Setting key
+ value: Setting value
+ user_id: ID of user making the change (for audit)
+
+ Returns:
+ The created/updated AdminSetting
+ """
+ setting_info = self.SETTINGS_MAP.get(key, {})
+
+ admin_setting = db.query(AdminSetting).filter_by(key=key).first()
+ if admin_setting:
+ admin_setting.value = value
+ if user_id:
+ admin_setting.last_modified_by_user_id = user_id
+ else:
+ admin_setting = AdminSetting(
+ key=key,
+ value=value,
+ value_type="string",
+ category=setting_info.get("category", "system"),
+ description=setting_info.get("description", ""),
+ last_modified_by_user_id=user_id,
+ )
+ db.add(admin_setting)
+
+ db.commit() # noqa: SVC-006 - Setting change is atomic, commit is intentional
+ db.refresh(admin_setting)
+
+ logger.info(f"Platform setting '{key}' set to '{value}' by user {user_id}")
+ return admin_setting
+
+ def get_all_storefront_settings(self, db: Session) -> dict[str, Any]:
+ """
+ Get all storefront settings with their current values and metadata.
+
+ Useful for admin UI to display current settings.
+
+ Returns:
+ Dict with setting info including current value and source
+ """
+ result = {}
+ for key, info in self.SETTINGS_MAP.items():
+ if info.get("category") == "storefront":
+ current_value = self.get(db, key)
+
+ # Determine source
+ admin_setting = db.query(AdminSetting).filter_by(key=key).first()
+ if admin_setting and admin_setting.value:
+ source = "database"
+ elif hasattr(settings, info.get("config_attr", "")):
+ source = "environment"
+ else:
+ source = "default"
+
+ result[key] = {
+ "value": current_value,
+ "source": source,
+ "description": info.get("description", ""),
+ "default": info.get("default"),
+ }
+
+ return result
+
+
+# Singleton instance
+platform_settings_service = PlatformSettingsService()
diff --git a/app/templates/shop/account/order-detail.html b/app/templates/shop/account/order-detail.html
index 84e9ffcb..4447c725 100644
--- a/app/templates/shop/account/order-detail.html
+++ b/app/templates/shop/account/order-detail.html
@@ -462,13 +462,7 @@ function shopOrderDetailPage() {
return this.statuses[status]?.class || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200';
},
- formatPrice(amount) {
- if (!amount && amount !== 0) return '-';
- return new Intl.NumberFormat('fr-LU', {
- style: 'currency',
- currency: 'EUR'
- }).format(amount);
- },
+ // formatPrice is inherited from shopLayoutData() via spread operator
formatDateTime(dateStr) {
if (!dateStr) return '-';
diff --git a/app/templates/shop/account/orders.html b/app/templates/shop/account/orders.html
index 5a6f190b..9ef080b3 100644
--- a/app/templates/shop/account/orders.html
+++ b/app/templates/shop/account/orders.html
@@ -217,13 +217,7 @@ function shopOrdersPage() {
return this.statuses[status]?.class || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200';
},
- formatPrice(amount) {
- if (!amount && amount !== 0) return '-';
- return new Intl.NumberFormat('fr-LU', {
- style: 'currency',
- currency: 'EUR'
- }).format(amount);
- },
+ // formatPrice is inherited from shopLayoutData() via spread operator
formatDate(dateStr) {
if (!dateStr) return '-';
diff --git a/app/templates/shop/account/profile.html b/app/templates/shop/account/profile.html
index 75e8000b..6ad376b5 100644
--- a/app/templates/shop/account/profile.html
+++ b/app/templates/shop/account/profile.html
@@ -523,13 +523,7 @@ function shopProfilePage() {
}
},
- formatPrice(amount) {
- if (!amount && amount !== 0) return '-';
- return new Intl.NumberFormat('fr-LU', {
- style: 'currency',
- currency: 'EUR'
- }).format(amount);
- },
+ // formatPrice is inherited from shopLayoutData() via spread operator
formatDate(dateStr) {
if (!dateStr) return '-';
diff --git a/app/templates/shop/base.html b/app/templates/shop/base.html
index c9e07c74..23e9e07e 100644
--- a/app/templates/shop/base.html
+++ b/app/templates/shop/base.html
@@ -308,19 +308,28 @@
{# 1. Log Configuration (must load first) #}
- {# 2. Icon System #}
+ {# 2. Global Shop Configuration (currency/locale settings) #}
+
+
+ {# 3. Icon System #}
- {# 3. Base Shop Layout (Alpine.js component - must load before Alpine) #}
+ {# 4. Base Shop Layout (Alpine.js component - must load before Alpine) #}
- {# 4. Utilities #}
+ {# 5. Utilities #}
- {# 5. API Client #}
+ {# 6. API Client #}
- {# 6. Alpine.js with CDN fallback (deferred - loads last) #}
+ {# 7. Alpine.js with CDN fallback (deferred - loads last) #}
- {# 7. Page-specific JavaScript #}
+ {# 8. Page-specific JavaScript #}
{% block extra_scripts %}{% endblock %}
{# Toast notification container #}
diff --git a/app/templates/shop/products.html b/app/templates/shop/products.html
index 8265bfa7..4a1424d9 100644
--- a/app/templates/shop/products.html
+++ b/app/templates/shop/products.html
@@ -207,13 +207,7 @@ document.addEventListener('alpine:init', () => {
this.loadProducts();
},
- formatPrice(amount) {
- if (!amount && amount !== 0) return '';
- return new Intl.NumberFormat('fr-LU', {
- style: 'currency',
- currency: 'EUR'
- }).format(amount);
- },
+ // formatPrice is inherited from shopLayoutData() via spread operator
async addToCart(product) {
console.log('[SHOP] Adding to cart:', product);
diff --git a/docs/deployment/launch-readiness.md b/docs/deployment/launch-readiness.md
index 7d6e7b82..e5d88873 100644
--- a/docs/deployment/launch-readiness.md
+++ b/docs/deployment/launch-readiness.md
@@ -54,6 +54,7 @@ The OMS is nearly production ready with core order processing, invoicing, invent
| Customer profile | Complete | Full profile management |
| Customer addresses | Complete | Multiple addresses, address book |
| Customer messages | Complete | Conversation-based messaging |
+| Currency locale | Complete | Configurable platform/vendor locale |
### Vendor Dashboard (90% Complete)
@@ -123,6 +124,18 @@ The OMS is nearly production ready with core order processing, invoicing, invent
- Integration tests for shop addresses API
- All validation scripts fixed and passing
+### Configurable Currency Locale
+- **Two-tier settings architecture**: Platform defaults with vendor overrides
+- **Resolution chain**: Vendor setting → AdminSetting → Environment → Hardcoded fallback
+- **Platform settings service**: New `PlatformSettingsService` for setting resolution
+- **Configuration options**:
+ - Environment: `DEFAULT_STOREFRONT_LOCALE`, `DEFAULT_CURRENCY` in `.env`
+ - Admin: `default_storefront_locale`, `default_currency` in AdminSetting table
+ - Vendor: `storefront_locale` field on Vendor model (nullable = inherit)
+- **Supported locales**: fr-LU, de-DE, de-LU, en-GB, nl-BE, etc.
+- **Frontend**: `window.SHOP_CONFIG` provides locale/currency to JavaScript
+- **Shared formatPrice()**: Single implementation in `shop-layout.js`
+
---
## Remaining Gaps
diff --git a/models/database/vendor.py b/models/database/vendor.py
index 0ffc7fd2..f3064aef 100644
--- a/models/database/vendor.py
+++ b/models/database/vendor.py
@@ -121,6 +121,10 @@ class Vendor(Base, TimestampMixin):
JSON, nullable=False, default=["fr", "de", "en"]
) # Array of enabled languages for storefront language selector
+ # Currency/number formatting locale (e.g., 'fr-LU' = "29,99 €", 'en-GB' = "€29.99")
+ # NULL means inherit from platform default (AdminSetting 'default_storefront_locale')
+ storefront_locale = Column(String(10), nullable=True)
+
# ========================================================================
# Relationships
# ========================================================================
diff --git a/models/schema/vendor.py b/models/schema/vendor.py
index f121f6c3..95cb9915 100644
--- a/models/schema/vendor.py
+++ b/models/schema/vendor.py
@@ -81,6 +81,11 @@ class VendorCreate(BaseModel):
storefront_languages: list[str] | None = Field(
default=["fr", "de", "en"], description="Enabled languages for storefront"
)
+ storefront_locale: str | None = Field(
+ None,
+ description="Locale for currency/number formatting (e.g., 'fr-LU', 'de-DE'). NULL = inherit from platform default",
+ max_length=10,
+ )
@field_validator("subdomain")
@classmethod
@@ -152,6 +157,11 @@ class VendorUpdate(BaseModel):
storefront_languages: list[str] | None = Field(
None, description="Enabled languages for storefront"
)
+ storefront_locale: str | None = Field(
+ None,
+ description="Locale for currency/number formatting (e.g., 'fr-LU', 'de-DE'). NULL = inherit from platform default",
+ max_length=10,
+ )
@field_validator("subdomain")
@classmethod
@@ -197,6 +207,9 @@ class VendorResponse(BaseModel):
storefront_language: str = "fr"
storefront_languages: list[str] = ["fr", "de", "en"]
+ # Currency/number formatting locale (NULL = inherit from platform default)
+ storefront_locale: str | None = None
+
# Timestamps
created_at: datetime
updated_at: datetime
diff --git a/scripts/validate_architecture.py b/scripts/validate_architecture.py
index 45d0cccd..a43e5a0c 100755
--- a/scripts/validate_architecture.py
+++ b/scripts/validate_architecture.py
@@ -2862,6 +2862,9 @@ class ArchitectureValidator:
# JS-012: Check for double /api/v1 prefix in API calls
self._check_api_prefix_usage(file_path, content, lines)
+ # JS-013: Check that components overriding init() call parent init
+ self._check_parent_init_call(file_path, content, lines)
+
def _check_platform_settings_usage(
self, file_path: Path, content: str, lines: list[str]
):
@@ -2995,6 +2998,59 @@ class ArchitectureValidator:
suggestion="Change '/api/v1/admin/...' to '/admin/...'",
)
+ def _check_parent_init_call(
+ self, file_path: Path, content: str, lines: list[str]
+ ):
+ """
+ JS-013: Check that vendor components overriding init() call parent init.
+
+ When a component uses ...data() to inherit base layout functionality AND
+ defines its own init() method, it MUST call the parent init first to set
+ critical properties like vendorCode.
+
+ Note: This only applies to vendor JS files because the vendor data() has
+ an init() method that extracts vendorCode from URL. Admin data() does not.
+ """
+ # Only check vendor JS files (admin data() doesn't have init())
+ if "/vendor/js/" not in str(file_path):
+ return
+
+ # Skip files that shouldn't have this pattern
+ excluded_files = ["init-alpine.js", "login.js", "onboarding.js"]
+ if file_path.name in excluded_files:
+ return
+
+ # Check if file uses ...data() spread operator
+ uses_data_spread = "...data()" in content
+
+ # Check if file defines its own async init()
+ has_own_init = re.search(r"async\s+init\s*\(\s*\)", content)
+
+ # If both conditions are true, check for parent init call
+ if uses_data_spread and has_own_init:
+ # Check for parent init call patterns
+ calls_parent_init = (
+ "data().init" in content
+ or "parentInit" in content
+ or "parent.init" in content
+ )
+
+ if not calls_parent_init:
+ # Find the line with async init() to report
+ for i, line in enumerate(lines, 1):
+ if re.search(r"async\s+init\s*\(\s*\)", line):
+ self._add_violation(
+ rule_id="JS-013",
+ rule_name="Components overriding init() must call parent init",
+ severity=Severity.ERROR,
+ file_path=file_path,
+ line_number=i,
+ message="Component with ...data() must call parent init() to set vendorCode",
+ context=line.strip()[:80],
+ suggestion="Add: const parentInit = data().init; if (parentInit) { await parentInit.call(this); }",
+ )
+ break
+
def _validate_templates(self, target_path: Path):
"""Validate template patterns"""
print("📄 Validating templates...")
diff --git a/static/shop/js/shop-layout.js b/static/shop/js/shop-layout.js
index dd93f19f..f2ad4568 100644
--- a/static/shop/js/shop-layout.js
+++ b/static/shop/js/shop-layout.js
@@ -219,12 +219,15 @@ function shopLayoutData() {
}, 3000);
},
- // Format currency
- formatPrice(price) {
- return new Intl.NumberFormat('en-US', {
+ // Format currency using configured locale
+ formatPrice(amount) {
+ if (!amount && amount !== 0) return '';
+ const locale = window.SHOP_CONFIG?.locale || 'fr-LU';
+ const currency = window.SHOP_CONFIG?.currency || 'EUR';
+ return new Intl.NumberFormat(locale, {
style: 'currency',
- currency: 'USD'
- }).format(price);
+ currency: currency
+ }).format(amount);
},
// Format date
diff --git a/static/vendor/js/analytics.js b/static/vendor/js/analytics.js
index cadc1aec..5eb2ae4d 100644
--- a/static/vendor/js/analytics.js
+++ b/static/vendor/js/analytics.js
@@ -58,6 +58,12 @@ function vendorAnalytics() {
}
window._vendorAnalyticsInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
await this.loadAllData();
} catch (error) {
diff --git a/static/vendor/js/billing.js b/static/vendor/js/billing.js
index a174396d..91610579 100644
--- a/static/vendor/js/billing.js
+++ b/static/vendor/js/billing.js
@@ -33,6 +33,12 @@ function vendorBilling() {
if (window._vendorBillingInitialized) return;
window._vendorBillingInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
// Check URL params for success/cancel
const params = new URLSearchParams(window.location.search);
diff --git a/static/vendor/js/content-page-edit.js b/static/vendor/js/content-page-edit.js
index e980e62d..4488d30a 100644
--- a/static/vendor/js/content-page-edit.js
+++ b/static/vendor/js/content-page-edit.js
@@ -44,6 +44,12 @@ function vendorContentPageEditor(pageId) {
}
window._vendorContentPageEditInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
contentPageEditLog.info('=== VENDOR CONTENT PAGE EDITOR INITIALIZING ===');
contentPageEditLog.info('Page ID:', this.pageId);
diff --git a/static/vendor/js/content-pages.js b/static/vendor/js/content-pages.js
index 6eb093a0..7e5d71f2 100644
--- a/static/vendor/js/content-pages.js
+++ b/static/vendor/js/content-pages.js
@@ -36,6 +36,12 @@ function vendorContentPagesManager() {
}
window._vendorContentPagesInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
await this.loadPages();
contentPagesLog.info('=== VENDOR CONTENT PAGES MANAGER INITIALIZATION COMPLETE ===');
diff --git a/static/vendor/js/customers.js b/static/vendor/js/customers.js
index d00e33af..0676143f 100644
--- a/static/vendor/js/customers.js
+++ b/static/vendor/js/customers.js
@@ -106,6 +106,12 @@ function vendorCustomers() {
}
window._vendorCustomersInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
// Load platform settings for rows per page
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
diff --git a/static/vendor/js/inventory.js b/static/vendor/js/inventory.js
index e4b90496..a184a1f9 100644
--- a/static/vendor/js/inventory.js
+++ b/static/vendor/js/inventory.js
@@ -137,6 +137,12 @@ function vendorInventory() {
}
window._vendorInventoryInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
// Load platform settings for rows per page
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
diff --git a/static/vendor/js/messages.js b/static/vendor/js/messages.js
index c7e59323..dfd984a4 100644
--- a/static/vendor/js/messages.js
+++ b/static/vendor/js/messages.js
@@ -65,6 +65,12 @@ function vendorMessages(initialConversationId = null) {
if (window._vendorMessagesInitialized) return;
window._vendorMessagesInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
messagesLog.debug('Initializing vendor messages page');
await Promise.all([
diff --git a/static/vendor/js/notifications.js b/static/vendor/js/notifications.js
index 2f6ad223..0037288b 100644
--- a/static/vendor/js/notifications.js
+++ b/static/vendor/js/notifications.js
@@ -60,6 +60,12 @@ function vendorNotifications() {
}
window._vendorNotificationsInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
await this.loadNotifications();
} catch (error) {
diff --git a/static/vendor/js/order-detail.js b/static/vendor/js/order-detail.js
index f59a47b3..2042dcee 100644
--- a/static/vendor/js/order-detail.js
+++ b/static/vendor/js/order-detail.js
@@ -62,6 +62,12 @@ function vendorOrderDetail() {
}
window._orderDetailInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
if (!this.orderId) {
this.error = 'Order ID not provided';
this.loading = false;
diff --git a/static/vendor/js/orders.js b/static/vendor/js/orders.js
index bfd09384..e9668715 100644
--- a/static/vendor/js/orders.js
+++ b/static/vendor/js/orders.js
@@ -137,6 +137,12 @@ function vendorOrders() {
}
window._vendorOrdersInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
// Load platform settings for rows per page
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
diff --git a/static/vendor/js/products.js b/static/vendor/js/products.js
index d31ad52c..1949dff9 100644
--- a/static/vendor/js/products.js
+++ b/static/vendor/js/products.js
@@ -121,6 +121,12 @@ function vendorProducts() {
}
window._vendorProductsInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
// Load platform settings for rows per page
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
diff --git a/static/vendor/js/profile.js b/static/vendor/js/profile.js
index fe15b6b0..d72a7279 100644
--- a/static/vendor/js/profile.js
+++ b/static/vendor/js/profile.js
@@ -54,6 +54,12 @@ function vendorProfile() {
}
window._vendorProfileInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
await this.loadProfile();
} catch (error) {
diff --git a/static/vendor/js/settings.js b/static/vendor/js/settings.js
index cdd3ba61..9302b74c 100644
--- a/static/vendor/js/settings.js
+++ b/static/vendor/js/settings.js
@@ -68,6 +68,12 @@ function vendorSettings() {
}
window._vendorSettingsInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
await this.loadSettings();
} catch (error) {
diff --git a/static/vendor/js/team.js b/static/vendor/js/team.js
index 559ca98a..0b18d1bd 100644
--- a/static/vendor/js/team.js
+++ b/static/vendor/js/team.js
@@ -73,6 +73,12 @@ function vendorTeam() {
}
window._vendorTeamInitialized = true;
+ // IMPORTANT: Call parent init first to set vendorCode from URL
+ const parentInit = data().init;
+ if (parentInit) {
+ await parentInit.call(this);
+ }
+
try {
await Promise.all([
this.loadMembers(),