feat: add configurable currency locale and fix vendor JS init

Currency Locale Configuration:
- Add platform-level storefront settings (locale, currency)
- Create PlatformSettingsService with resolution chain:
  vendor → AdminSetting → environment → hardcoded fallback
- Add storefront_locale nullable field to Vendor model
- Update shop routes to resolve and pass locale to templates
- Add window.SHOP_CONFIG for frontend JavaScript access
- Centralize formatPrice() in shop-layout.js using SHOP_CONFIG
- Remove local formatPrice functions from shop templates

Vendor JS Bug Fix:
- Fix vendorCode being null on all vendor pages
- Root cause: page components overriding init() without calling parent
- Add parent init call to 14 vendor JS files
- Add JS-013 architecture rule to prevent future regressions
- Validator now checks vendor JS files for parent init pattern

Files changed:
- New: app/services/platform_settings_service.py
- New: alembic/versions/s7a8b9c0d1e2_add_storefront_locale_to_vendors.py
- Modified: 14 vendor JS files, shop templates, validation scripts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-02 21:26:12 +01:00
parent d9d34ab102
commit c87bdfa129
30 changed files with 522 additions and 48 deletions

View File

@@ -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...")