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:
@@ -1,7 +1,8 @@
|
||||
# app/modules/catalog/definition.py
|
||||
"""Catalog module definition."""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from app.modules.base import MenuItemDefinition, MenuSectionDefinition, ModuleDefinition
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
module = ModuleDefinition(
|
||||
code="catalog",
|
||||
@@ -10,4 +11,25 @@ module = ModuleDefinition(
|
||||
version="1.0.0",
|
||||
is_self_contained=True,
|
||||
requires=["inventory"],
|
||||
# New module-driven menu definitions
|
||||
menus={
|
||||
FrontendType.VENDOR: [
|
||||
MenuSectionDefinition(
|
||||
id="products",
|
||||
label_key="catalog.menu.products_inventory",
|
||||
icon="package",
|
||||
order=10,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="products",
|
||||
label_key="catalog.menu.all_products",
|
||||
icon="shopping-bag",
|
||||
route="/vendor/{vendor_code}/products",
|
||||
order=10,
|
||||
is_mandatory=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
{
|
||||
"title": "Produktkatalog",
|
||||
"description": "Produktkatalogverwaltung für Händler",
|
||||
"products": {
|
||||
"title": "Produkte",
|
||||
"subtitle": "Verwalten Sie Ihren Produktkatalog",
|
||||
"create": "Produkt erstellen",
|
||||
"edit": "Produkt bearbeiten",
|
||||
"delete": "Produkt löschen",
|
||||
"empty": "Keine Produkte gefunden",
|
||||
"empty_search": "Keine Produkte entsprechen Ihrer Suche"
|
||||
},
|
||||
"product": {
|
||||
"name": "Produktname",
|
||||
"description": "Beschreibung",
|
||||
"sku": "Artikelnummer",
|
||||
"product": "Produkt",
|
||||
"add_product": "Produkt hinzufügen",
|
||||
"edit_product": "Produkt bearbeiten",
|
||||
"delete_product": "Produkt löschen",
|
||||
"product_name": "Produktname",
|
||||
"product_code": "Produktcode",
|
||||
"sku": "SKU",
|
||||
"price": "Preis",
|
||||
"stock": "Bestand",
|
||||
"status": "Status",
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv"
|
||||
},
|
||||
"media": {
|
||||
"title": "Produktmedien",
|
||||
"upload": "Bild hochladen",
|
||||
"delete": "Bild löschen",
|
||||
"primary": "Als Hauptbild festlegen",
|
||||
"error": "Medien-Upload fehlgeschlagen"
|
||||
},
|
||||
"validation": {
|
||||
"name_required": "Produktname ist erforderlich",
|
||||
"price_required": "Preis ist erforderlich",
|
||||
"invalid_sku": "Ungültiges Artikelnummernformat",
|
||||
"duplicate_sku": "Artikelnummer existiert bereits"
|
||||
"sale_price": "Verkaufspreis",
|
||||
"cost": "Kosten",
|
||||
"stock": "Lagerbestand",
|
||||
"in_stock": "Auf Lager",
|
||||
"out_of_stock": "Nicht auf Lager",
|
||||
"low_stock": "Geringer Bestand",
|
||||
"availability": "Verfügbarkeit",
|
||||
"available": "Verfügbar",
|
||||
"unavailable": "Nicht verfügbar",
|
||||
"brand": "Marke",
|
||||
"category": "Kategorie",
|
||||
"categories": "Kategorien",
|
||||
"image": "Bild",
|
||||
"images": "Bilder",
|
||||
"main_image": "Hauptbild",
|
||||
"gallery": "Galerie",
|
||||
"weight": "Gewicht",
|
||||
"dimensions": "Abmessungen",
|
||||
"color": "Farbe",
|
||||
"size": "Größe",
|
||||
"material": "Material",
|
||||
"condition": "Zustand",
|
||||
"new": "Neu",
|
||||
"used": "Gebraucht",
|
||||
"refurbished": "Generalüberholt",
|
||||
"no_products": "Keine Produkte gefunden",
|
||||
"search_products": "Produkte suchen...",
|
||||
"filter_by_category": "Nach Kategorie filtern",
|
||||
"filter_by_status": "Nach Status filtern",
|
||||
"sort_by": "Sortieren nach",
|
||||
"sort_newest": "Neueste",
|
||||
"sort_oldest": "Älteste",
|
||||
"sort_price_low": "Preis: Niedrig bis Hoch",
|
||||
"sort_price_high": "Preis: Hoch bis Niedrig",
|
||||
"sort_name_az": "Name: A-Z",
|
||||
"sort_name_za": "Name: Z-A"
|
||||
},
|
||||
"messages": {
|
||||
"created": "Produkt erfolgreich erstellt",
|
||||
"updated": "Produkt erfolgreich aktualisiert",
|
||||
"deleted": "Produkt erfolgreich gelöscht",
|
||||
"not_found": "Produkt nicht gefunden",
|
||||
"cannot_delete": "Produkt kann nicht gelöscht werden",
|
||||
"error_loading": "Fehler beim Laden der Produkte"
|
||||
},
|
||||
"filters": {
|
||||
"all_products": "Alle Produkte",
|
||||
"active_only": "Nur aktive",
|
||||
"search_placeholder": "Produkte suchen..."
|
||||
"product_deleted_successfully": "Product deleted successfully",
|
||||
"please_fill_in_all_required_fields": "Please fill in all required fields",
|
||||
"product_updated_successfully": "Product updated successfully",
|
||||
"failed_to_load_media_library": "Failed to load media library",
|
||||
"no_vendor_associated_with_this_product": "No vendor associated with this product",
|
||||
"please_select_an_image_file": "Please select an image file",
|
||||
"image_must_be_less_than_10mb": "Image must be less than 10MB",
|
||||
"image_uploaded_successfully": "Image uploaded successfully",
|
||||
"product_removed_from_vendor_catalog": "Product removed from vendor catalog.",
|
||||
"please_select_a_vendor": "Please select a vendor",
|
||||
"please_enter_a_product_title_english": "Please enter a product title (English)",
|
||||
"product_created_successfully": "Product created successfully",
|
||||
"please_select_a_vendor_first": "Please select a vendor first"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
{
|
||||
"title": "Catalogue produits",
|
||||
"description": "Gestion du catalogue produits pour les vendeurs",
|
||||
"products": {
|
||||
"title": "Produits",
|
||||
"subtitle": "Gérez votre catalogue de produits",
|
||||
"create": "Créer un produit",
|
||||
"edit": "Modifier le produit",
|
||||
"delete": "Supprimer le produit",
|
||||
"empty": "Aucun produit trouvé",
|
||||
"empty_search": "Aucun produit ne correspond à votre recherche"
|
||||
},
|
||||
"product": {
|
||||
"name": "Nom du produit",
|
||||
"description": "Description",
|
||||
"sku": "Référence",
|
||||
"product": "Produit",
|
||||
"add_product": "Ajouter un produit",
|
||||
"edit_product": "Modifier le produit",
|
||||
"delete_product": "Supprimer le produit",
|
||||
"product_name": "Nom du produit",
|
||||
"product_code": "Code produit",
|
||||
"sku": "SKU",
|
||||
"price": "Prix",
|
||||
"sale_price": "Prix de vente",
|
||||
"cost": "Coût",
|
||||
"stock": "Stock",
|
||||
"status": "Statut",
|
||||
"active": "Actif",
|
||||
"inactive": "Inactif"
|
||||
},
|
||||
"media": {
|
||||
"title": "Médias du produit",
|
||||
"upload": "Télécharger une image",
|
||||
"delete": "Supprimer l'image",
|
||||
"primary": "Définir comme image principale",
|
||||
"error": "Échec du téléchargement"
|
||||
},
|
||||
"validation": {
|
||||
"name_required": "Le nom du produit est requis",
|
||||
"price_required": "Le prix est requis",
|
||||
"invalid_sku": "Format de référence invalide",
|
||||
"duplicate_sku": "La référence existe déjà"
|
||||
"in_stock": "En stock",
|
||||
"out_of_stock": "Rupture de stock",
|
||||
"low_stock": "Stock faible",
|
||||
"availability": "Disponibilité",
|
||||
"available": "Disponible",
|
||||
"unavailable": "Indisponible",
|
||||
"brand": "Marque",
|
||||
"category": "Catégorie",
|
||||
"categories": "Catégories",
|
||||
"image": "Image",
|
||||
"images": "Images",
|
||||
"main_image": "Image principale",
|
||||
"gallery": "Galerie",
|
||||
"weight": "Poids",
|
||||
"dimensions": "Dimensions",
|
||||
"color": "Couleur",
|
||||
"size": "Taille",
|
||||
"material": "Matériau",
|
||||
"condition": "État",
|
||||
"new": "Neuf",
|
||||
"used": "Occasion",
|
||||
"refurbished": "Reconditionné",
|
||||
"no_products": "Aucun produit trouvé",
|
||||
"search_products": "Rechercher des produits...",
|
||||
"filter_by_category": "Filtrer par catégorie",
|
||||
"filter_by_status": "Filtrer par statut",
|
||||
"sort_by": "Trier par",
|
||||
"sort_newest": "Plus récent",
|
||||
"sort_oldest": "Plus ancien",
|
||||
"sort_price_low": "Prix : croissant",
|
||||
"sort_price_high": "Prix : décroissant",
|
||||
"sort_name_az": "Nom : A-Z",
|
||||
"sort_name_za": "Nom : Z-A"
|
||||
},
|
||||
"messages": {
|
||||
"created": "Produit créé avec succès",
|
||||
"updated": "Produit mis à jour avec succès",
|
||||
"deleted": "Produit supprimé avec succès",
|
||||
"not_found": "Produit non trouvé",
|
||||
"cannot_delete": "Impossible de supprimer le produit",
|
||||
"error_loading": "Erreur lors du chargement des produits"
|
||||
},
|
||||
"filters": {
|
||||
"all_products": "Tous les produits",
|
||||
"active_only": "Actifs uniquement",
|
||||
"search_placeholder": "Rechercher des produits..."
|
||||
"product_deleted_successfully": "Product deleted successfully",
|
||||
"please_fill_in_all_required_fields": "Please fill in all required fields",
|
||||
"product_updated_successfully": "Product updated successfully",
|
||||
"failed_to_load_media_library": "Failed to load media library",
|
||||
"no_vendor_associated_with_this_product": "No vendor associated with this product",
|
||||
"please_select_an_image_file": "Please select an image file",
|
||||
"image_must_be_less_than_10mb": "Image must be less than 10MB",
|
||||
"image_uploaded_successfully": "Image uploaded successfully",
|
||||
"product_removed_from_vendor_catalog": "Product removed from vendor catalog.",
|
||||
"please_select_a_vendor": "Please select a vendor",
|
||||
"please_enter_a_product_title_english": "Please enter a product title (English)",
|
||||
"product_created_successfully": "Product created successfully",
|
||||
"please_select_a_vendor_first": "Please select a vendor first"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
{
|
||||
"title": "Produktkatalog",
|
||||
"description": "Produktkatalogverwaltung fir Händler",
|
||||
"products": {
|
||||
"title": "Produkter",
|
||||
"subtitle": "Verwalte Äre Produktkatalog",
|
||||
"create": "Produkt erstellen",
|
||||
"edit": "Produkt beaarbechten",
|
||||
"delete": "Produkt läschen",
|
||||
"empty": "Keng Produkter fonnt",
|
||||
"empty_search": "Keng Produkter entspriechen Ärer Sich"
|
||||
},
|
||||
"product": {
|
||||
"name": "Produktnumm",
|
||||
"description": "Beschreiwung",
|
||||
"sku": "Artikelnummer",
|
||||
"product": "Produkt",
|
||||
"add_product": "Produkt derbäisetzen",
|
||||
"edit_product": "Produkt änneren",
|
||||
"delete_product": "Produkt läschen",
|
||||
"product_name": "Produktnumm",
|
||||
"product_code": "Produktcode",
|
||||
"sku": "SKU",
|
||||
"price": "Präis",
|
||||
"stock": "Bestand",
|
||||
"status": "Status",
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv"
|
||||
},
|
||||
"media": {
|
||||
"title": "Produktmedien",
|
||||
"upload": "Bild eroplueden",
|
||||
"delete": "Bild läschen",
|
||||
"primary": "Als Haaptbild setzen",
|
||||
"error": "Medien-Upload feelgeschloen"
|
||||
},
|
||||
"validation": {
|
||||
"name_required": "Produktnumm ass erfuerderlech",
|
||||
"price_required": "Präis ass erfuerderlech",
|
||||
"invalid_sku": "Ongëlteg Artikelnummerformat",
|
||||
"duplicate_sku": "Artikelnummer existéiert schonn"
|
||||
"sale_price": "Verkafspräis",
|
||||
"cost": "Käschten",
|
||||
"stock": "Lager",
|
||||
"in_stock": "Op Lager",
|
||||
"out_of_stock": "Net op Lager",
|
||||
"low_stock": "Niddregen Stock",
|
||||
"availability": "Disponibilitéit",
|
||||
"available": "Disponibel",
|
||||
"unavailable": "Net disponibel",
|
||||
"brand": "Mark",
|
||||
"category": "Kategorie",
|
||||
"categories": "Kategorien",
|
||||
"image": "Bild",
|
||||
"images": "Biller",
|
||||
"main_image": "Haaptbild",
|
||||
"gallery": "Galerie",
|
||||
"weight": "Gewiicht",
|
||||
"dimensions": "Dimensiounen",
|
||||
"color": "Faarf",
|
||||
"size": "Gréisst",
|
||||
"material": "Material",
|
||||
"condition": "Zoustand",
|
||||
"new": "Nei",
|
||||
"used": "Gebraucht",
|
||||
"refurbished": "Iwwerholl",
|
||||
"no_products": "Keng Produkter fonnt",
|
||||
"search_products": "Produkter sichen...",
|
||||
"filter_by_category": "No Kategorie filteren",
|
||||
"filter_by_status": "No Status filteren",
|
||||
"sort_by": "Sortéieren no",
|
||||
"sort_newest": "Neisten",
|
||||
"sort_oldest": "Eelsten",
|
||||
"sort_price_low": "Präis: Niddreg op Héich",
|
||||
"sort_price_high": "Präis: Héich op Niddreg",
|
||||
"sort_name_az": "Numm: A-Z",
|
||||
"sort_name_za": "Numm: Z-A"
|
||||
},
|
||||
"messages": {
|
||||
"created": "Produkt erfollegräich erstallt",
|
||||
"updated": "Produkt erfollegräich aktualiséiert",
|
||||
"deleted": "Produkt erfollegräich geläscht",
|
||||
"not_found": "Produkt net fonnt",
|
||||
"cannot_delete": "Produkt kann net geläscht ginn",
|
||||
"error_loading": "Feeler beim Lueden vun de Produkter"
|
||||
},
|
||||
"filters": {
|
||||
"all_products": "All Produkter",
|
||||
"active_only": "Nëmmen aktiv",
|
||||
"search_placeholder": "Produkter sichen..."
|
||||
"product_deleted_successfully": "Product deleted successfully",
|
||||
"please_fill_in_all_required_fields": "Please fill in all required fields",
|
||||
"product_updated_successfully": "Product updated successfully",
|
||||
"failed_to_load_media_library": "Failed to load media library",
|
||||
"no_vendor_associated_with_this_product": "No vendor associated with this product",
|
||||
"please_select_an_image_file": "Please select an image file",
|
||||
"image_must_be_less_than_10mb": "Image must be less than 10MB",
|
||||
"image_uploaded_successfully": "Image uploaded successfully",
|
||||
"product_removed_from_vendor_catalog": "Product removed from vendor catalog.",
|
||||
"please_select_a_vendor": "Please select a vendor",
|
||||
"please_enter_a_product_title_english": "Please enter a product title (English)",
|
||||
"product_created_successfully": "Product created successfully",
|
||||
"please_select_a_vendor_first": "Please select a vendor first"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ from app.modules.catalog.schemas import (
|
||||
ProductResponse,
|
||||
)
|
||||
from middleware.vendor_context import require_vendor_context
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -15,8 +15,8 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_db, require_menu_access
|
||||
from app.modules.core.utils.page_context import get_admin_context
|
||||
from app.templates_config import templates
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
from models.database.user import User
|
||||
from app.modules.enums import FrontendType
|
||||
from app.modules.tenancy.models import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_vendor_from_cookie_or_header, get_db
|
||||
from app.modules.core.utils.page_context import get_vendor_context
|
||||
from app.templates_config import templates
|
||||
from models.database.user import User
|
||||
from app.modules.tenancy.models import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from sqlalchemy.orm import Session, joinedload
|
||||
|
||||
from app.modules.catalog.exceptions import ProductNotFoundException
|
||||
from app.modules.catalog.models import Product
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -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 = '';
|
||||
|
||||
@@ -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();
|
||||
|
||||
35
app/modules/catalog/static/vendor/js/products.js
vendored
35
app/modules/catalog/static/vendor/js/products.js
vendored
@@ -112,6 +112,9 @@ function vendorProducts() {
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Load i18n translations
|
||||
await I18n.loadModule('catalog');
|
||||
|
||||
vendorProductsLog.info('Products init() called');
|
||||
|
||||
// Guard against multiple initialization
|
||||
@@ -230,13 +233,13 @@ function vendorProducts() {
|
||||
await apiClient.put(`/vendor/products/${product.id}/toggle-active`);
|
||||
product.is_active = !product.is_active;
|
||||
Utils.showToast(
|
||||
product.is_active ? 'Product activated' : 'Product deactivated',
|
||||
product.is_active ? I18n.t('catalog.messages.product_activated') : I18n.t('catalog.messages.product_deactivated'),
|
||||
'success'
|
||||
);
|
||||
vendorProductsLog.info('Toggled product active:', product.id, product.is_active);
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Failed to toggle active:', error);
|
||||
Utils.showToast(error.message || 'Failed to update product', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_update_product'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -251,13 +254,13 @@ function vendorProducts() {
|
||||
await apiClient.put(`/vendor/products/${product.id}/toggle-featured`);
|
||||
product.is_featured = !product.is_featured;
|
||||
Utils.showToast(
|
||||
product.is_featured ? 'Product marked as featured' : 'Product unmarked as featured',
|
||||
product.is_featured ? I18n.t('catalog.messages.product_marked_as_featured') : I18n.t('catalog.messages.product_unmarked_as_featured'),
|
||||
'success'
|
||||
);
|
||||
vendorProductsLog.info('Toggled product featured:', product.id, product.is_featured);
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Failed to toggle featured:', error);
|
||||
Utils.showToast(error.message || 'Failed to update product', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_update_product'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -288,7 +291,7 @@ function vendorProducts() {
|
||||
this.saving = true;
|
||||
try {
|
||||
await apiClient.delete(`/vendor/products/${this.selectedProduct.id}`);
|
||||
Utils.showToast('Product deleted successfully', 'success');
|
||||
Utils.showToast(I18n.t('catalog.messages.product_deleted_successfully'), 'success');
|
||||
vendorProductsLog.info('Deleted product:', this.selectedProduct.id);
|
||||
|
||||
this.showDeleteModal = false;
|
||||
@@ -296,7 +299,7 @@ function vendorProducts() {
|
||||
await this.loadProducts();
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Failed to delete product:', error);
|
||||
Utils.showToast(error.message || 'Failed to delete product', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_delete_product'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -417,12 +420,12 @@ function vendorProducts() {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
Utils.showToast(`${successCount} product(s) activated`, 'success');
|
||||
Utils.showToast(I18n.t('catalog.messages.products_activated', { count: successCount }), 'success');
|
||||
this.clearSelection();
|
||||
await this.loadProducts();
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Bulk activate failed:', error);
|
||||
Utils.showToast(error.message || 'Failed to activate products', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_activate_products'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -445,12 +448,12 @@ function vendorProducts() {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
Utils.showToast(`${successCount} product(s) deactivated`, 'success');
|
||||
Utils.showToast(I18n.t('catalog.messages.products_deactivated', { count: successCount }), 'success');
|
||||
this.clearSelection();
|
||||
await this.loadProducts();
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Bulk deactivate failed:', error);
|
||||
Utils.showToast(error.message || 'Failed to deactivate products', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_deactivate_products'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -473,12 +476,12 @@ function vendorProducts() {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
Utils.showToast(`${successCount} product(s) marked as featured`, 'success');
|
||||
Utils.showToast(I18n.t('catalog.messages.products_marked_as_featured', { count: successCount }), 'success');
|
||||
this.clearSelection();
|
||||
await this.loadProducts();
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Bulk set featured failed:', error);
|
||||
Utils.showToast(error.message || 'Failed to update products', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_update_product'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -501,12 +504,12 @@ function vendorProducts() {
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
Utils.showToast(`${successCount} product(s) unmarked as featured`, 'success');
|
||||
Utils.showToast(I18n.t('catalog.messages.products_unmarked_as_featured', { count: successCount }), 'success');
|
||||
this.clearSelection();
|
||||
await this.loadProducts();
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Bulk remove featured failed:', error);
|
||||
Utils.showToast(error.message || 'Failed to update products', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_update_product'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -533,13 +536,13 @@ function vendorProducts() {
|
||||
await apiClient.delete(`/vendor/products/${productId}`);
|
||||
successCount++;
|
||||
}
|
||||
Utils.showToast(`${successCount} product(s) deleted`, 'success');
|
||||
Utils.showToast(I18n.t('catalog.messages.products_deleted', { count: successCount }), 'success');
|
||||
this.showBulkDeleteModal = false;
|
||||
this.clearSelection();
|
||||
await this.loadProducts();
|
||||
} catch (error) {
|
||||
vendorProductsLog.error('Bulk delete failed:', error);
|
||||
Utils.showToast(error.message || 'Failed to delete products', 'error');
|
||||
Utils.showToast(error.message || I18n.t('catalog.messages.failed_to_delete_product'), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user