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:
@@ -6,8 +6,8 @@ Defines the orders module including its features, menu items,
|
||||
route configurations, and self-contained module settings.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
from app.modules.base import MenuItemDefinition, MenuSectionDefinition, ModuleDefinition
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
@@ -54,6 +54,44 @@ orders_module = ModuleDefinition(
|
||||
"orders", # Vendor order management
|
||||
],
|
||||
},
|
||||
# New module-driven menu definitions
|
||||
menus={
|
||||
FrontendType.ADMIN: [
|
||||
MenuSectionDefinition(
|
||||
id="vendorOps",
|
||||
label_key="orders.menu.vendor_operations",
|
||||
icon="clipboard-list",
|
||||
order=40,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="orders",
|
||||
label_key="orders.menu.orders",
|
||||
icon="clipboard-list",
|
||||
route="/admin/orders",
|
||||
order=40,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
MenuSectionDefinition(
|
||||
id="sales",
|
||||
label_key="orders.menu.sales_orders",
|
||||
icon="document-text",
|
||||
order=20,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="orders",
|
||||
label_key="orders.menu.orders",
|
||||
icon="document-text",
|
||||
route="/vendor/{vendor_code}/orders",
|
||||
order=10,
|
||||
is_mandatory=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
is_core=False,
|
||||
# =========================================================================
|
||||
# Self-Contained Module Configuration
|
||||
|
||||
@@ -1 +1,50 @@
|
||||
{}
|
||||
{
|
||||
"orders": {
|
||||
"title": "Bestellungen",
|
||||
"order": "Bestellung",
|
||||
"order_id": "Bestellnummer",
|
||||
"order_number": "Bestellnummer",
|
||||
"order_date": "Bestelldatum",
|
||||
"order_status": "Bestellstatus",
|
||||
"order_details": "Bestelldetails",
|
||||
"order_items": "Bestellartikel",
|
||||
"order_total": "Bestellsumme",
|
||||
"subtotal": "Zwischensumme",
|
||||
"shipping": "Versand",
|
||||
"tax": "Steuer",
|
||||
"discount": "Rabatt",
|
||||
"customer": "Kunde",
|
||||
"shipping_address": "Lieferadresse",
|
||||
"billing_address": "Rechnungsadresse",
|
||||
"payment_method": "Zahlungsmethode",
|
||||
"payment_status": "Zahlungsstatus",
|
||||
"tracking": "Sendungsverfolgung",
|
||||
"tracking_number": "Sendungsnummer",
|
||||
"carrier": "Versanddienstleister",
|
||||
"no_orders": "Keine Bestellungen gefunden",
|
||||
"search_orders": "Bestellungen suchen...",
|
||||
"filter_by_status": "Nach Status filtern",
|
||||
"status_pending": "Ausstehend",
|
||||
"status_processing": "In Bearbeitung",
|
||||
"status_shipped": "Versendet",
|
||||
"status_delivered": "Zugestellt",
|
||||
"status_cancelled": "Storniert",
|
||||
"status_refunded": "Erstattet",
|
||||
"status_confirmed": "Bestätigt",
|
||||
"status_rejected": "Abgelehnt",
|
||||
"confirm_order": "Bestellung bestätigen",
|
||||
"reject_order": "Bestellung ablehnen",
|
||||
"set_tracking": "Sendungsverfolgung setzen",
|
||||
"view_details": "Details ansehen"
|
||||
},
|
||||
"messages": {
|
||||
"order_status_updated": "Order status updated",
|
||||
"item_shipped_successfully": "Item shipped successfully",
|
||||
"all_items_shipped": "All items shipped",
|
||||
"invoice_downloaded": "Invoice downloaded",
|
||||
"failed_to_load_order_details": "Failed to load order details.",
|
||||
"order_status_updated_successfully": "Order status updated successfully.",
|
||||
"order_marked_as_shipped_successfully": "Order marked as shipped successfully.",
|
||||
"no_shipping_label_url_available_for_this": "No shipping label URL available for this order."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,50 @@
|
||||
{}
|
||||
{
|
||||
"orders": {
|
||||
"title": "Orders",
|
||||
"order": "Order",
|
||||
"order_id": "Order ID",
|
||||
"order_number": "Order Number",
|
||||
"order_date": "Order Date",
|
||||
"order_status": "Order Status",
|
||||
"order_details": "Order Details",
|
||||
"order_items": "Order Items",
|
||||
"order_total": "Order Total",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Shipping",
|
||||
"tax": "Tax",
|
||||
"discount": "Discount",
|
||||
"customer": "Customer",
|
||||
"shipping_address": "Shipping Address",
|
||||
"billing_address": "Billing Address",
|
||||
"payment_method": "Payment Method",
|
||||
"payment_status": "Payment Status",
|
||||
"tracking": "Tracking",
|
||||
"tracking_number": "Tracking Number",
|
||||
"carrier": "Carrier",
|
||||
"no_orders": "No orders found",
|
||||
"search_orders": "Search orders...",
|
||||
"filter_by_status": "Filter by status",
|
||||
"status_pending": "Pending",
|
||||
"status_processing": "Processing",
|
||||
"status_shipped": "Shipped",
|
||||
"status_delivered": "Delivered",
|
||||
"status_cancelled": "Cancelled",
|
||||
"status_refunded": "Refunded",
|
||||
"status_confirmed": "Confirmed",
|
||||
"status_rejected": "Rejected",
|
||||
"confirm_order": "Confirm Order",
|
||||
"reject_order": "Reject Order",
|
||||
"set_tracking": "Set Tracking",
|
||||
"view_details": "View Details"
|
||||
},
|
||||
"messages": {
|
||||
"order_status_updated": "Order status updated",
|
||||
"item_shipped_successfully": "Item shipped successfully",
|
||||
"all_items_shipped": "All items shipped",
|
||||
"invoice_downloaded": "Invoice downloaded",
|
||||
"failed_to_load_order_details": "Failed to load order details.",
|
||||
"order_status_updated_successfully": "Order status updated successfully.",
|
||||
"order_marked_as_shipped_successfully": "Order marked as shipped successfully.",
|
||||
"no_shipping_label_url_available_for_this": "No shipping label URL available for this order."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,50 @@
|
||||
{}
|
||||
{
|
||||
"orders": {
|
||||
"title": "Commandes",
|
||||
"order": "Commande",
|
||||
"order_id": "ID de commande",
|
||||
"order_number": "Numéro de commande",
|
||||
"order_date": "Date de commande",
|
||||
"order_status": "Statut de la commande",
|
||||
"order_details": "Détails de la commande",
|
||||
"order_items": "Articles de la commande",
|
||||
"order_total": "Total de la commande",
|
||||
"subtotal": "Sous-total",
|
||||
"shipping": "Livraison",
|
||||
"tax": "Taxe",
|
||||
"discount": "Remise",
|
||||
"customer": "Client",
|
||||
"shipping_address": "Adresse de livraison",
|
||||
"billing_address": "Adresse de facturation",
|
||||
"payment_method": "Mode de paiement",
|
||||
"payment_status": "Statut du paiement",
|
||||
"tracking": "Suivi",
|
||||
"tracking_number": "Numéro de suivi",
|
||||
"carrier": "Transporteur",
|
||||
"no_orders": "Aucune commande trouvée",
|
||||
"search_orders": "Rechercher des commandes...",
|
||||
"filter_by_status": "Filtrer par statut",
|
||||
"status_pending": "En attente",
|
||||
"status_processing": "En cours",
|
||||
"status_shipped": "Expédiée",
|
||||
"status_delivered": "Livrée",
|
||||
"status_cancelled": "Annulée",
|
||||
"status_refunded": "Remboursée",
|
||||
"status_confirmed": "Confirmée",
|
||||
"status_rejected": "Rejetée",
|
||||
"confirm_order": "Confirmer la commande",
|
||||
"reject_order": "Rejeter la commande",
|
||||
"set_tracking": "Définir le suivi",
|
||||
"view_details": "Voir les détails"
|
||||
},
|
||||
"messages": {
|
||||
"order_status_updated": "Order status updated",
|
||||
"item_shipped_successfully": "Item shipped successfully",
|
||||
"all_items_shipped": "All items shipped",
|
||||
"invoice_downloaded": "Invoice downloaded",
|
||||
"failed_to_load_order_details": "Failed to load order details.",
|
||||
"order_status_updated_successfully": "Order status updated successfully.",
|
||||
"order_marked_as_shipped_successfully": "Order marked as shipped successfully.",
|
||||
"no_shipping_label_url_available_for_this": "No shipping label URL available for this order."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,50 @@
|
||||
{}
|
||||
{
|
||||
"orders": {
|
||||
"title": "Bestellungen",
|
||||
"order": "Bestellung",
|
||||
"order_id": "Bestellungs-ID",
|
||||
"order_number": "Bestellungsnummer",
|
||||
"order_date": "Bestellungsdatum",
|
||||
"order_status": "Bestellungsstatus",
|
||||
"order_details": "Bestellungsdetailer",
|
||||
"order_items": "Bestellungsartikelen",
|
||||
"order_total": "Bestellungstotal",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Versand",
|
||||
"tax": "Steier",
|
||||
"discount": "Rabatt",
|
||||
"customer": "Client",
|
||||
"shipping_address": "Liwweradress",
|
||||
"billing_address": "Rechnungsadress",
|
||||
"payment_method": "Bezuelmethod",
|
||||
"payment_status": "Bezuelstatus",
|
||||
"tracking": "Tracking",
|
||||
"tracking_number": "Trackingnummer",
|
||||
"carrier": "Transporteur",
|
||||
"no_orders": "Keng Bestellunge fonnt",
|
||||
"search_orders": "Bestellunge sichen...",
|
||||
"filter_by_status": "No Status filteren",
|
||||
"status_pending": "Aussteesend",
|
||||
"status_processing": "A Veraarbechtung",
|
||||
"status_shipped": "Verschéckt",
|
||||
"status_delivered": "Geliwwert",
|
||||
"status_cancelled": "Annuléiert",
|
||||
"status_refunded": "Rembourséiert",
|
||||
"status_confirmed": "Bestätegt",
|
||||
"status_rejected": "Ofgeleent",
|
||||
"confirm_order": "Bestellung bestätegen",
|
||||
"reject_order": "Bestellung oflehnen",
|
||||
"set_tracking": "Tracking setzen",
|
||||
"view_details": "Detailer kucken"
|
||||
},
|
||||
"messages": {
|
||||
"order_status_updated": "Order status updated",
|
||||
"item_shipped_successfully": "Item shipped successfully",
|
||||
"all_items_shipped": "All items shipped",
|
||||
"invoice_downloaded": "Invoice downloaded",
|
||||
"failed_to_load_order_details": "Failed to load order details.",
|
||||
"order_status_updated_successfully": "Order status updated successfully.",
|
||||
"order_marked_as_shipped_successfully": "Order marked as shipped successfully.",
|
||||
"no_shipping_label_url_available_for_this": "No shipping label URL available for this order."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,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()
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ from app.modules.orders.schemas.invoice import (
|
||||
VendorInvoiceSettingsCreate,
|
||||
VendorInvoiceSettingsUpdate,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ from app.utils.vat import (
|
||||
)
|
||||
from app.modules.marketplace.models import MarketplaceProduct, MarketplaceProductTranslation
|
||||
from app.modules.catalog.models import Product
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
# Placeholder product constants
|
||||
PLACEHOLDER_GTIN = "0000000000000"
|
||||
|
||||
@@ -135,6 +135,9 @@ function adminOrders() {
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Load i18n translations
|
||||
await I18n.loadModule('orders');
|
||||
|
||||
adminOrdersLog.info('Orders init() called');
|
||||
|
||||
// Guard against multiple initialization
|
||||
@@ -392,7 +395,7 @@ function adminOrders() {
|
||||
this.showDetailModal = true;
|
||||
} catch (error) {
|
||||
adminOrdersLog.error('Failed to load order details:', error);
|
||||
Utils.showToast('Failed to load order details.', 'error');
|
||||
Utils.showToast(I18n.t('orders.messages.failed_to_load_order_details'), 'error');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -436,7 +439,7 @@ function adminOrders() {
|
||||
this.showStatusModal = false;
|
||||
this.selectedOrder = null;
|
||||
|
||||
Utils.showToast('Order status updated successfully.', 'success');
|
||||
Utils.showToast(I18n.t('orders.messages.order_status_updated_successfully'), 'success');
|
||||
|
||||
await this.refresh();
|
||||
} catch (error) {
|
||||
@@ -487,7 +490,7 @@ function adminOrders() {
|
||||
this.showMarkAsShippedModal = false;
|
||||
this.selectedOrder = null;
|
||||
|
||||
Utils.showToast('Order marked as shipped successfully.', 'success');
|
||||
Utils.showToast(I18n.t('orders.messages.order_marked_as_shipped_successfully'), 'success');
|
||||
|
||||
await this.refresh();
|
||||
} catch (error) {
|
||||
@@ -509,7 +512,7 @@ function adminOrders() {
|
||||
// Open label URL in new tab
|
||||
window.open(labelInfo.label_url, '_blank');
|
||||
} else {
|
||||
Utils.showToast('No shipping label URL available for this order.', 'warning');
|
||||
Utils.showToast(I18n.t('orders.messages.no_shipping_label_url_available_for_this'), 'warning');
|
||||
}
|
||||
} catch (error) {
|
||||
adminOrdersLog.error('Failed to get shipping label:', error);
|
||||
|
||||
@@ -53,6 +53,9 @@ function vendorOrderDetail() {
|
||||
],
|
||||
|
||||
async init() {
|
||||
// Load i18n translations
|
||||
await I18n.loadModule('orders');
|
||||
|
||||
orderDetailLog.info('Order detail init() called, orderId:', this.orderId);
|
||||
|
||||
// Guard against multiple initialization
|
||||
@@ -262,7 +265,7 @@ function vendorOrderDetail() {
|
||||
{}
|
||||
);
|
||||
|
||||
Utils.showToast('Item shipped successfully', 'success');
|
||||
Utils.showToast(I18n.t('orders.messages.item_shipped_successfully'), 'success');
|
||||
await this.loadOrderDetails();
|
||||
|
||||
} catch (error) {
|
||||
@@ -301,7 +304,7 @@ function vendorOrderDetail() {
|
||||
payload
|
||||
);
|
||||
|
||||
Utils.showToast('All items shipped', 'success');
|
||||
Utils.showToast(I18n.t('orders.messages.all_items_shipped'), 'success');
|
||||
this.showShipAllModal = false;
|
||||
this.trackingNumber = '';
|
||||
this.trackingProvider = '';
|
||||
@@ -368,7 +371,7 @@ function vendorOrderDetail() {
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
|
||||
Utils.showToast('Invoice downloaded', 'success');
|
||||
Utils.showToast(I18n.t('orders.messages.invoice_downloaded'), 'success');
|
||||
|
||||
} catch (error) {
|
||||
orderDetailLog.error('Failed to download invoice PDF:', error);
|
||||
|
||||
@@ -128,6 +128,9 @@ function vendorOrders() {
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Load i18n translations
|
||||
await I18n.loadModule('orders');
|
||||
|
||||
vendorOrdersLog.info('Orders init() called');
|
||||
|
||||
// Guard against multiple initialization
|
||||
@@ -277,7 +280,7 @@ function vendorOrders() {
|
||||
status: this.newStatus
|
||||
});
|
||||
|
||||
Utils.showToast('Order status updated', 'success');
|
||||
Utils.showToast(I18n.t('orders.messages.order_status_updated'), 'success');
|
||||
vendorOrdersLog.info('Updated order status:', this.selectedOrder.id, this.newStatus);
|
||||
|
||||
this.showStatusModal = false;
|
||||
|
||||
Reference in New Issue
Block a user