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:
127
scripts/add_i18n_module_loading.py
Normal file
127
scripts/add_i18n_module_loading.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Add I18n.loadModule() calls to JS files that use I18n.t().
|
||||
This ensures module translations are loaded before use.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
MODULES_DIR = PROJECT_ROOT / "app" / "modules"
|
||||
|
||||
# Pattern to find I18n.t('module.xxx') calls and extract module name
|
||||
I18N_PATTERN = re.compile(r"I18n\.t\(['\"](\w+)\.")
|
||||
|
||||
|
||||
def find_modules_used(content: str) -> set[str]:
|
||||
"""Find all modules referenced in I18n.t() calls."""
|
||||
return set(I18N_PATTERN.findall(content))
|
||||
|
||||
|
||||
def add_module_loading(js_file: Path):
|
||||
"""Add I18n.loadModule() calls to a JS file."""
|
||||
content = js_file.read_text(encoding="utf-8")
|
||||
|
||||
# Find modules used in this file
|
||||
modules = find_modules_used(content)
|
||||
if not modules:
|
||||
return False
|
||||
|
||||
# Check if already has module loading
|
||||
if "I18n.loadModule(" in content:
|
||||
return False
|
||||
|
||||
# Find the init() method and add loading there
|
||||
# Look for common patterns:
|
||||
# 1. init() { ... }
|
||||
# 2. async init() { ... }
|
||||
|
||||
init_patterns = [
|
||||
# Pattern for "init() {" or "async init() {"
|
||||
(r"((?:async\s+)?init\s*\(\s*\)\s*\{)", "init"),
|
||||
# Pattern for "mounted() {" (Vue style)
|
||||
(r"(mounted\s*\(\s*\)\s*\{)", "mounted"),
|
||||
]
|
||||
|
||||
for pattern, method_name in init_patterns:
|
||||
match = re.search(pattern, content)
|
||||
if match:
|
||||
# Generate loading code
|
||||
load_calls = "\n".join(f" await I18n.loadModule('{m}');" for m in sorted(modules))
|
||||
|
||||
# If init is not async, we need to make it async
|
||||
full_match = match.group(1)
|
||||
if "async" not in full_match:
|
||||
# Make init async
|
||||
new_init = full_match.replace(f"{method_name}()", f"async {method_name}()")
|
||||
content = content.replace(full_match, new_init)
|
||||
full_match = new_init
|
||||
|
||||
# Add loading after the opening brace
|
||||
insert_code = f"\n // Load i18n translations\n{load_calls}\n"
|
||||
content = content.replace(full_match, full_match + insert_code)
|
||||
|
||||
js_file.write_text(content, encoding="utf-8")
|
||||
return True
|
||||
|
||||
# If no init found, add at file level (for simpler scripts)
|
||||
# This handles files that don't use Alpine components
|
||||
if "function " in content or "const " in content:
|
||||
# Find first function definition
|
||||
func_match = re.search(r"^(function\s+\w+\s*\([^)]*\)\s*\{)", content, re.MULTILINE)
|
||||
if func_match:
|
||||
load_calls = "\n".join(f" await I18n.loadModule('{m}');" for m in sorted(modules))
|
||||
|
||||
# Make function async if needed
|
||||
full_match = func_match.group(1)
|
||||
if "async" not in full_match:
|
||||
new_func = full_match.replace("function ", "async function ")
|
||||
content = content.replace(full_match, new_func)
|
||||
full_match = new_func
|
||||
|
||||
insert_code = f"\n // Load i18n translations\n{load_calls}\n"
|
||||
content = content.replace(full_match, full_match + insert_code)
|
||||
|
||||
js_file.write_text(content, encoding="utf-8")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("Adding I18n.loadModule() to JS files")
|
||||
print("=" * 70)
|
||||
|
||||
updated = 0
|
||||
skipped = 0
|
||||
|
||||
# Find all JS files with I18n.t() calls
|
||||
for js_file in MODULES_DIR.rglob("*.js"):
|
||||
content = js_file.read_text(encoding="utf-8")
|
||||
if "I18n.t(" not in content:
|
||||
continue
|
||||
|
||||
modules = find_modules_used(content)
|
||||
if not modules:
|
||||
continue
|
||||
|
||||
rel_path = js_file.relative_to(PROJECT_ROOT)
|
||||
|
||||
if add_module_loading(js_file):
|
||||
print(f" Updated: {rel_path} (modules: {', '.join(sorted(modules))})")
|
||||
updated += 1
|
||||
else:
|
||||
print(f" Skipped: {rel_path} (already has loading or no init method)")
|
||||
skipped += 1
|
||||
|
||||
print()
|
||||
print(f"Updated: {updated} files")
|
||||
print(f"Skipped: {skipped} files")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -23,7 +23,9 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.utils.money import cents_to_euros, euros_to_cents
|
||||
from models.database import Order, OrderItem, Product, Vendor
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
|
||||
def generate_order_number():
|
||||
|
||||
@@ -18,7 +18,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.cms.models import ContentPage
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
|
||||
def create_landing_page(
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Debug script for historical import issues."""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.marketplace.services.letzshop.credentials_service import LetzshopCredentialsService
|
||||
from app.modules.marketplace.models import LetzshopHistoricalImportJob
|
||||
|
||||
|
||||
def get_valid_shipment_states(vendor_id: int = 1):
|
||||
"""Query the GraphQL schema to find valid ShipmentStateEnum values."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
creds_service = LetzshopCredentialsService(db)
|
||||
|
||||
# Introspection query to get enum values
|
||||
introspection_query = """
|
||||
query {
|
||||
__type(name: "ShipmentStateEnum") {
|
||||
name
|
||||
enumValues {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
with creds_service.create_client(vendor_id) as client:
|
||||
print("Querying ShipmentStateEnum values...")
|
||||
result = client._execute(introspection_query)
|
||||
|
||||
if result and "__type" in result:
|
||||
type_info = result["__type"]
|
||||
if type_info:
|
||||
print(f"\nEnum: {type_info['name']}")
|
||||
print("Valid values:")
|
||||
for value in type_info.get("enumValues", []):
|
||||
print(f" - {value['name']}: {value.get('description', 'No description')}")
|
||||
else:
|
||||
print("ShipmentStateEnum type not found")
|
||||
else:
|
||||
print(f"Unexpected result: {result}")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def check_import_job_status():
|
||||
"""Check the status of historical import jobs."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
jobs = db.query(LetzshopHistoricalImportJob).order_by(
|
||||
LetzshopHistoricalImportJob.id.desc()
|
||||
).limit(5).all()
|
||||
|
||||
print("\n=== Recent Historical Import Jobs ===")
|
||||
for job in jobs:
|
||||
print(f"\nJob {job.id}:")
|
||||
print(f" Status: {job.status}")
|
||||
print(f" Phase: {job.current_phase}")
|
||||
print(f" Page: {job.current_page}")
|
||||
print(f" Shipments fetched: {job.shipments_fetched}")
|
||||
print(f" Orders processed: {job.orders_processed}")
|
||||
print(f" Orders imported: {job.orders_imported}")
|
||||
print(f" Orders updated: {job.orders_updated}")
|
||||
print(f" Orders skipped: {job.orders_skipped}")
|
||||
print(f" Confirmed stats: {job.confirmed_stats}")
|
||||
print(f" Declined stats: {job.declined_stats}")
|
||||
print(f" Error: {job.error_message}")
|
||||
print(f" Started: {job.started_at}")
|
||||
print(f" Completed: {job.completed_at}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_fetch_states(vendor_id: int = 1):
|
||||
"""Test fetching shipments with different states."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
creds_service = LetzshopCredentialsService(db)
|
||||
|
||||
# States to test
|
||||
states_to_test = ["unconfirmed", "confirmed", "declined", "shipped", "rejected"]
|
||||
|
||||
with creds_service.create_client(vendor_id) as client:
|
||||
for state in states_to_test:
|
||||
print(f"\nTesting state: {state}")
|
||||
try:
|
||||
# Just try to fetch first page
|
||||
shipments = client.get_all_shipments_paginated(
|
||||
state=state,
|
||||
page_size=1,
|
||||
max_pages=1,
|
||||
)
|
||||
print(f" ✓ Success: {len(shipments)} shipments")
|
||||
except Exception as e:
|
||||
print(f" ✗ Error: {e}")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def check_declined_items(vendor_id: int = 1):
|
||||
"""Check for orders with declined inventory units."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from models.database.letzshop import LetzshopOrder
|
||||
from collections import Counter
|
||||
|
||||
# Get all orders with inventory_units
|
||||
orders = db.query(LetzshopOrder).filter(
|
||||
LetzshopOrder.vendor_id == vendor_id,
|
||||
LetzshopOrder.inventory_units.isnot(None),
|
||||
).all()
|
||||
|
||||
# Count all states
|
||||
state_counts = Counter()
|
||||
total_units = 0
|
||||
|
||||
for order in orders:
|
||||
units = order.inventory_units or []
|
||||
for unit in units:
|
||||
state = unit.get("state", "unknown")
|
||||
state_counts[state] += 1
|
||||
total_units += 1
|
||||
|
||||
print(f"\n=== Inventory Unit States (all {len(orders)} orders) ===")
|
||||
print(f" Total units: {total_units}")
|
||||
print(f"\n State breakdown:")
|
||||
for state, count in sorted(state_counts.items(), key=lambda x: -x[1]):
|
||||
print(f" {state}: {count}")
|
||||
|
||||
# Show a sample order with its units
|
||||
if orders:
|
||||
sample = orders[0]
|
||||
print(f"\nSample order #{sample.id} (shipment {sample.letzshop_shipment_id}):")
|
||||
print(f" Shipment state: {sample.letzshop_state}")
|
||||
print(f" Sync status: {sample.sync_status}")
|
||||
if sample.inventory_units:
|
||||
for i, unit in enumerate(sample.inventory_units[:5]):
|
||||
print(f" Unit {i+1}: state={unit.get('state')}, id={unit.get('id')}")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Letzshop Historical Import Debug ===\n")
|
||||
|
||||
print("1. Checking valid shipment states...")
|
||||
get_valid_shipment_states()
|
||||
|
||||
print("\n\n2. Testing different state values...")
|
||||
test_fetch_states()
|
||||
|
||||
print("\n\n3. Checking import job status...")
|
||||
check_import_job_status()
|
||||
|
||||
print("\n\n4. Checking declined items in inventory units...")
|
||||
check_declined_items()
|
||||
@@ -8,7 +8,7 @@ Run this script to create default logging configuration settings.
|
||||
# Import all models to avoid SQLAlchemy relationship issues
|
||||
import models # noqa: F401
|
||||
from app.core.database import SessionLocal
|
||||
from models.database.admin import AdminSetting
|
||||
from app.modules.tenancy.models import AdminSetting
|
||||
|
||||
|
||||
def init_log_settings():
|
||||
|
||||
@@ -36,8 +36,8 @@ from app.core.database import SessionLocal
|
||||
from app.core.environment import is_production
|
||||
from app.core.permissions import PermissionGroups
|
||||
from middleware.auth import AuthManager
|
||||
from models.database.admin import AdminSetting
|
||||
from models.database.user import User
|
||||
from app.modules.tenancy.models import AdminSetting
|
||||
from app.modules.tenancy.models import User
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
|
||||
196
scripts/migrate_js_i18n.py
Normal file
196
scripts/migrate_js_i18n.py
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Migrate hardcoded toast messages in JS files to use I18n.t().
|
||||
Extracts messages, creates translation keys, and updates both JS and locale files.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
MODULES_DIR = PROJECT_ROOT / "app" / "modules"
|
||||
|
||||
# Languages to update
|
||||
LANGUAGES = ["en", "fr", "de", "lb"]
|
||||
|
||||
# Pattern to match Utils.showToast('message', 'type') or Utils.showToast("message", "type")
|
||||
TOAST_PATTERN = re.compile(r"Utils\.showToast\(['\"]([^'\"]+)['\"],\s*['\"](\w+)['\"]\)")
|
||||
|
||||
# Map module names to their message namespace
|
||||
MODULE_MESSAGE_NS = {
|
||||
"catalog": "catalog",
|
||||
"orders": "orders",
|
||||
"customers": "customers",
|
||||
"inventory": "inventory",
|
||||
"marketplace": "marketplace",
|
||||
"tenancy": "tenancy",
|
||||
"core": "core",
|
||||
"messaging": "messaging",
|
||||
"billing": "billing",
|
||||
"cms": "cms",
|
||||
"checkout": "checkout",
|
||||
"cart": "cart",
|
||||
"dev_tools": "dev_tools",
|
||||
"monitoring": "monitoring",
|
||||
"analytics": "analytics",
|
||||
}
|
||||
|
||||
|
||||
def message_to_key(message: str) -> str:
|
||||
"""Convert a message string to a translation key."""
|
||||
# Remove special characters and convert to snake_case
|
||||
key = message.lower()
|
||||
key = re.sub(r'[^\w\s]', '', key)
|
||||
key = re.sub(r'\s+', '_', key)
|
||||
# Truncate if too long
|
||||
if len(key) > 40:
|
||||
key = key[:40].rstrip('_')
|
||||
return key
|
||||
|
||||
|
||||
def find_js_files_with_toasts() -> dict[str, list[Path]]:
|
||||
"""Find all JS files with toast messages, grouped by module."""
|
||||
files_by_module = defaultdict(list)
|
||||
|
||||
for module_dir in sorted(MODULES_DIR.iterdir()):
|
||||
if not module_dir.is_dir():
|
||||
continue
|
||||
module_name = module_dir.name
|
||||
|
||||
# Find all JS files in this module
|
||||
for js_file in module_dir.rglob("*.js"):
|
||||
content = js_file.read_text(encoding="utf-8")
|
||||
if "Utils.showToast(" in content:
|
||||
files_by_module[module_name].append(js_file)
|
||||
|
||||
return dict(files_by_module)
|
||||
|
||||
|
||||
def extract_messages_from_file(js_file: Path) -> list[tuple[str, str]]:
|
||||
"""Extract all toast messages from a JS file."""
|
||||
content = js_file.read_text(encoding="utf-8")
|
||||
return TOAST_PATTERN.findall(content)
|
||||
|
||||
|
||||
def load_locale_file(module_path: Path, lang: str) -> dict:
|
||||
"""Load a module's locale file."""
|
||||
locale_file = module_path / "locales" / f"{lang}.json"
|
||||
if locale_file.exists():
|
||||
with open(locale_file, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
|
||||
def save_locale_file(module_path: Path, lang: str, data: dict):
|
||||
"""Save a module's locale file."""
|
||||
locale_file = module_path / "locales" / f"{lang}.json"
|
||||
locale_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(locale_file, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
def update_js_file(js_file: Path, module_name: str, message_keys: dict[str, str]):
|
||||
"""Update a JS file to use I18n.t() for toast messages."""
|
||||
content = js_file.read_text(encoding="utf-8")
|
||||
original = content
|
||||
|
||||
for message, key in message_keys.items():
|
||||
# Replace both single and double quoted versions
|
||||
full_key = f"{module_name}.messages.{key}"
|
||||
|
||||
# Pattern to match the exact message in Utils.showToast
|
||||
for quote in ["'", '"']:
|
||||
old = f"Utils.showToast({quote}{message}{quote},"
|
||||
new = f"Utils.showToast(I18n.t('{full_key}'),"
|
||||
content = content.replace(old, new)
|
||||
|
||||
if content != original:
|
||||
js_file.write_text(content, encoding="utf-8")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def process_module(module_name: str, js_files: list[Path]) -> dict[str, str]:
|
||||
"""Process all JS files for a module and return message->key mapping."""
|
||||
all_messages = {}
|
||||
|
||||
# Extract all unique messages
|
||||
for js_file in js_files:
|
||||
messages = extract_messages_from_file(js_file)
|
||||
for message, msg_type in messages:
|
||||
if message not in all_messages:
|
||||
all_messages[message] = message_to_key(message)
|
||||
|
||||
return all_messages
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("JS i18n Migration Script")
|
||||
print("=" * 70)
|
||||
|
||||
# Find all JS files with toasts
|
||||
files_by_module = find_js_files_with_toasts()
|
||||
|
||||
total_files = sum(len(files) for files in files_by_module.values())
|
||||
print(f"Found {total_files} JS files with toast messages across {len(files_by_module)} modules")
|
||||
print()
|
||||
|
||||
for module_name, js_files in sorted(files_by_module.items()):
|
||||
print(f"\n{'='*70}")
|
||||
print(f"Module: {module_name} ({len(js_files)} files)")
|
||||
print("=" * 70)
|
||||
|
||||
module_path = MODULES_DIR / module_name
|
||||
|
||||
# Process all JS files and get message mappings
|
||||
message_keys = process_module(module_name, js_files)
|
||||
|
||||
if not message_keys:
|
||||
print(" No messages found")
|
||||
continue
|
||||
|
||||
print(f" Found {len(message_keys)} unique messages:")
|
||||
for msg, key in sorted(message_keys.items(), key=lambda x: x[1]):
|
||||
print(f" {key}: {msg[:50]}{'...' if len(msg) > 50 else ''}")
|
||||
|
||||
# Update locale files for all languages
|
||||
print(f"\n Updating locale files...")
|
||||
for lang in LANGUAGES:
|
||||
locale_data = load_locale_file(module_path, lang)
|
||||
|
||||
# Add messages section if not exists
|
||||
if "messages" not in locale_data:
|
||||
locale_data["messages"] = {}
|
||||
|
||||
# Add each message (only add if not already present)
|
||||
for message, key in message_keys.items():
|
||||
if key not in locale_data["messages"]:
|
||||
# For English, use the original message
|
||||
# For other languages, we'll use the English as placeholder
|
||||
locale_data["messages"][key] = message
|
||||
|
||||
save_locale_file(module_path, lang, locale_data)
|
||||
print(f" Updated: {lang}.json")
|
||||
|
||||
# Update JS files
|
||||
print(f"\n Updating JS files...")
|
||||
for js_file in js_files:
|
||||
if update_js_file(js_file, module_name, message_keys):
|
||||
rel_path = js_file.relative_to(PROJECT_ROOT)
|
||||
print(f" Updated: {rel_path}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("Migration complete!")
|
||||
print("\nNext steps:")
|
||||
print("1. Review the generated message keys in locale files")
|
||||
print("2. Translate non-English messages (currently using English as placeholder)")
|
||||
print("3. Test the application to verify toast messages work")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -52,8 +52,8 @@ from app.core.database import SessionLocal
|
||||
from app.core.environment import get_environment, is_production
|
||||
from middleware.auth import AuthManager
|
||||
from app.modules.cms.models import ContentPage
|
||||
from models.database.admin import PlatformAlert
|
||||
from models.database.company import Company
|
||||
from app.modules.tenancy.models import PlatformAlert
|
||||
from app.modules.tenancy.models import Company
|
||||
from app.modules.customers.models.customer import Customer, CustomerAddress
|
||||
from app.modules.marketplace.models import (
|
||||
MarketplaceImportJob,
|
||||
@@ -62,10 +62,10 @@ from app.modules.marketplace.models import (
|
||||
)
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.catalog.models import Product
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, Vendor, VendorUser
|
||||
from models.database.vendor_domain import VendorDomain
|
||||
from models.database.vendor_theme import VendorTheme
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.tenancy.models import Role, Vendor, VendorUser
|
||||
from app.modules.tenancy.models import VendorDomain
|
||||
from app.modules.cms.models import VendorTheme
|
||||
|
||||
SEED_MODE = os.getenv("SEED_MODE", "normal") # normal, minimal, reset
|
||||
FORCE_RESET = os.getenv("FORCE_RESET", "false").lower() in ("true", "1", "yes")
|
||||
|
||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from app.core.database import get_db
|
||||
from models.database.email import EmailCategory, EmailTemplate
|
||||
from app.modules.messaging.models import EmailCategory, EmailTemplate
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_logging_endpoints():
|
||||
print("\n[3] Checking database logs...")
|
||||
try:
|
||||
from app.core.database import SessionLocal
|
||||
from models.database.admin import ApplicationLog
|
||||
from app.modules.tenancy.models import ApplicationLog
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
|
||||
@@ -4505,7 +4505,10 @@ class ArchitectureValidator:
|
||||
for py_file in schemas_path.glob("*.py"):
|
||||
if py_file.name == "__init__.py":
|
||||
continue
|
||||
# Allow auth.py (core authentication schemas)
|
||||
# Allow base.py (base schema classes - infrastructure)
|
||||
if py_file.name == "base.py":
|
||||
continue
|
||||
# Allow auth.py (core authentication schemas - cross-cutting)
|
||||
if py_file.name == "auth.py":
|
||||
continue
|
||||
|
||||
|
||||
@@ -146,8 +146,8 @@ def verify_model_structure():
|
||||
from app.modules.inventory.models import Inventory
|
||||
from app.modules.marketplace.models import MarketplaceImportJob, MarketplaceProduct
|
||||
from app.modules.catalog.models import Product
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.tenancy.models import Vendor
|
||||
|
||||
print("[OK] All database models imported successfully")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user