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

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