refactor: complete module-driven architecture migration

This commit completes the migration to a fully module-driven architecture:

## Models Migration
- Moved all domain models from models/database/ to their respective modules:
  - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc.
  - cms: MediaFile, VendorTheme
  - messaging: Email, VendorEmailSettings, VendorEmailTemplate
  - core: AdminMenuConfig
- models/database/ now only contains Base and TimestampMixin (infrastructure)

## Schemas Migration
- Moved all domain schemas from models/schema/ to their respective modules:
  - tenancy: company, vendor, admin, team, vendor_domain
  - cms: media, image, vendor_theme
  - messaging: email
- models/schema/ now only contains base.py and auth.py (infrastructure)

## Routes Migration
- Moved admin routes from app/api/v1/admin/ to modules:
  - menu_config.py -> core module
  - modules.py -> tenancy module
  - module_config.py -> tenancy module
- app/api/v1/admin/ now only aggregates auto-discovered module routes

## Menu System
- Implemented module-driven menu system with MenuDiscoveryService
- Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT
- Added MenuItemDefinition and MenuSectionDefinition dataclasses
- Each module now defines its own menu items in definition.py
- MenuService integrates with MenuDiscoveryService for template rendering

## Documentation
- Updated docs/architecture/models-structure.md
- Updated docs/architecture/menu-management.md
- Updated architecture validation rules for new exceptions

## Architecture Validation
- Updated MOD-019 rule to allow base.py in models/schema/
- Created core module exceptions.py and schemas/ directory
- All validation errors resolved (only warnings remain)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 21:02:56 +01:00
parent 09d7d282c6
commit d7a0ff8818
307 changed files with 5536 additions and 3826 deletions

View File

@@ -66,6 +66,9 @@ function adminVendorProductCreate() {
},
async init() {
// Load i18n translations
await I18n.loadModule('catalog');
adminVendorProductCreateLog.info('Vendor Product Create init() called');
// Guard against multiple initialization
@@ -166,12 +169,12 @@ function adminVendorProductCreate() {
*/
async createProduct() {
if (!this.form.vendor_id) {
Utils.showToast('Please select a vendor', 'error');
Utils.showToast(I18n.t('catalog.messages.please_select_a_vendor'), 'error');
return;
}
if (!this.form.translations.en.title?.trim()) {
Utils.showToast('Please enter a product title (English)', 'error');
Utils.showToast(I18n.t('catalog.messages.please_enter_a_product_title_english'), 'error');
return;
}
@@ -224,7 +227,7 @@ function adminVendorProductCreate() {
adminVendorProductCreateLog.info('Product created:', response.id);
Utils.showToast('Product created successfully', 'success');
Utils.showToast(I18n.t('catalog.messages.product_created_successfully'), 'success');
// Redirect to the new product's detail page
setTimeout(() => {
@@ -232,7 +235,7 @@ function adminVendorProductCreate() {
}, 1000);
} catch (error) {
adminVendorProductCreateLog.error('Failed to create product:', error);
Utils.showToast(error.message || 'Failed to create product', 'error');
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_create_product'), 'error');
} finally {
this.saving = false;
}
@@ -274,7 +277,7 @@ function adminVendorProductCreate() {
this.mediaPickerState.total = response.total || 0;
} catch (error) {
adminVendorProductCreateLog.error('Failed to load media library:', error);
Utils.showToast('Failed to load media library', 'error');
Utils.showToast(I18n.t('catalog.messages.failed_to_load_media_library'), 'error');
} finally {
this.mediaPickerState.loading = false;
}
@@ -326,17 +329,17 @@ function adminVendorProductCreate() {
const vendorId = this.form?.vendor_id;
if (!vendorId) {
Utils.showToast('Please select a vendor first', 'error');
Utils.showToast(I18n.t('catalog.messages.please_select_a_vendor_first'), 'error');
return;
}
if (!file.type.startsWith('image/')) {
Utils.showToast('Please select an image file', 'error');
Utils.showToast(I18n.t('catalog.messages.please_select_an_image_file'), 'error');
return;
}
if (file.size > 10 * 1024 * 1024) {
Utils.showToast('Image must be less than 10MB', 'error');
Utils.showToast(I18n.t('catalog.messages.image_must_be_less_than_10mb'), 'error');
return;
}
@@ -355,11 +358,11 @@ function adminVendorProductCreate() {
this.mediaPickerState.media.unshift(response.media);
this.mediaPickerState.total++;
this.toggleMediaSelection(response.media);
Utils.showToast('Image uploaded successfully', 'success');
Utils.showToast(I18n.t('catalog.messages.image_uploaded_successfully'), 'success');
}
} catch (error) {
adminVendorProductCreateLog.error('Failed to upload image:', error);
Utils.showToast(error.message || 'Failed to upload image', 'error');
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_upload_image'), 'error');
} finally {
this.mediaPickerState.uploading = false;
event.target.value = '';

View File

@@ -76,6 +76,9 @@ function adminVendorProductEdit() {
},
async init() {
// Load i18n translations
await I18n.loadModule('catalog');
adminVendorProductEditLog.info('Vendor Product Edit init() called, ID:', this.productId);
// Guard against multiple initialization
@@ -209,7 +212,7 @@ function adminVendorProductEdit() {
*/
async saveProduct() {
if (!this.isFormValid()) {
Utils.showToast('Please fill in all required fields', 'error');
Utils.showToast(I18n.t('catalog.messages.please_fill_in_all_required_fields'), 'error');
return;
}
@@ -266,7 +269,7 @@ function adminVendorProductEdit() {
adminVendorProductEditLog.info('Product saved:', this.productId);
Utils.showToast('Product updated successfully', 'success');
Utils.showToast(I18n.t('catalog.messages.product_updated_successfully'), 'success');
// Redirect to detail page
setTimeout(() => {
@@ -274,7 +277,7 @@ function adminVendorProductEdit() {
}, 1000);
} catch (error) {
adminVendorProductEditLog.error('Failed to save product:', error);
Utils.showToast(error.message || 'Failed to save product', 'error');
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_save_product'), 'error');
} finally {
this.saving = false;
}
@@ -316,7 +319,7 @@ function adminVendorProductEdit() {
this.mediaPickerState.total = response.total || 0;
} catch (error) {
adminVendorProductEditLog.error('Failed to load media library:', error);
Utils.showToast('Failed to load media library', 'error');
Utils.showToast(I18n.t('catalog.messages.failed_to_load_media_library'), 'error');
} finally {
this.mediaPickerState.loading = false;
}
@@ -368,17 +371,17 @@ function adminVendorProductEdit() {
const vendorId = this.product?.vendor_id;
if (!vendorId) {
Utils.showToast('No vendor associated with this product', 'error');
Utils.showToast(I18n.t('catalog.messages.no_vendor_associated_with_this_product'), 'error');
return;
}
if (!file.type.startsWith('image/')) {
Utils.showToast('Please select an image file', 'error');
Utils.showToast(I18n.t('catalog.messages.please_select_an_image_file'), 'error');
return;
}
if (file.size > 10 * 1024 * 1024) {
Utils.showToast('Image must be less than 10MB', 'error');
Utils.showToast(I18n.t('catalog.messages.image_must_be_less_than_10mb'), 'error');
return;
}
@@ -397,11 +400,11 @@ function adminVendorProductEdit() {
this.mediaPickerState.media.unshift(response.media);
this.mediaPickerState.total++;
this.toggleMediaSelection(response.media);
Utils.showToast('Image uploaded successfully', 'success');
Utils.showToast(I18n.t('catalog.messages.image_uploaded_successfully'), 'success');
}
} catch (error) {
adminVendorProductEditLog.error('Failed to upload image:', error);
Utils.showToast(error.message || 'Failed to upload image', 'error');
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_upload_image'), 'error');
} finally {
this.mediaPickerState.uploading = false;
event.target.value = '';

View File

@@ -116,6 +116,9 @@ function adminVendorProducts() {
},
async init() {
// Load i18n translations
await I18n.loadModule('catalog');
adminVendorProductsLog.info('Vendor Products init() called');
// Guard against multiple initialization
@@ -385,7 +388,7 @@ function adminVendorProducts() {
this.productToRemove = null;
// Show success notification
Utils.showToast('Product removed from vendor catalog.', 'success');
Utils.showToast(I18n.t('catalog.messages.product_removed_from_vendor_catalog'), 'success');
// Refresh the list
await this.refresh();