diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py
index 223a83c5..530fa36b 100644
--- a/app/api/v1/__init__.py
+++ b/app/api/v1/__init__.py
@@ -3,6 +3,6 @@
API Version 1 - All endpoints
"""
-from . import admin, shop, vendor
+from . import admin, storefront, vendor
-__all__ = ["admin", "vendor", "shop"]
+__all__ = ["admin", "vendor", "storefront"]
diff --git a/app/api/v1/admin/code_quality.py b/app/api/v1/admin/code_quality.py
index 5d76f8d5..cac3cfe1 100644
--- a/app/api/v1/admin/code_quality.py
+++ b/app/api/v1/admin/code_quality.py
@@ -21,7 +21,7 @@ from app.services.code_quality_service import (
from app.tasks.code_quality_tasks import execute_code_quality_scan
from models.database.architecture_scan import ArchitectureScan
from models.database.user import User
-from models.schema.stats import CodeQualityDashboardStatsResponse
+from app.modules.analytics.schemas import CodeQualityDashboardStatsResponse
router = APIRouter()
diff --git a/app/api/v1/admin/dashboard.py b/app/api/v1/admin/dashboard.py
index ffcdf9d5..1be31d38 100644
--- a/app/api/v1/admin/dashboard.py
+++ b/app/api/v1/admin/dashboard.py
@@ -13,7 +13,7 @@ from app.core.database import get_db
from app.services.admin_service import admin_service
from app.services.stats_service import stats_service
from models.database.user import User
-from models.schema.stats import (
+from app.modules.analytics.schemas import (
AdminDashboardResponse,
ImportStatsResponse,
MarketplaceStatsResponse,
diff --git a/app/api/v1/admin/marketplace.py b/app/api/v1/admin/marketplace.py
index fbc78801..620a4c08 100644
--- a/app/api/v1/admin/marketplace.py
+++ b/app/api/v1/admin/marketplace.py
@@ -24,7 +24,7 @@ from models.schema.marketplace_import_job import (
MarketplaceImportJobRequest,
MarketplaceImportJobResponse,
)
-from models.schema.stats import ImportStatsResponse
+from app.modules.analytics.schemas import ImportStatsResponse
router = APIRouter(prefix="/marketplace-import-jobs")
logger = logging.getLogger(__name__)
diff --git a/app/api/v1/admin/notifications.py b/app/api/v1/admin/notifications.py
index 0d8193d6..656aaae6 100644
--- a/app/api/v1/admin/notifications.py
+++ b/app/api/v1/admin/notifications.py
@@ -29,7 +29,7 @@ from models.schema.admin import (
PlatformAlertResolve,
PlatformAlertResponse,
)
-from models.schema.notification import (
+from app.modules.messaging.schemas import (
AlertStatisticsResponse,
MessageResponse,
UnreadCountResponse,
diff --git a/app/api/v1/admin/vendors.py b/app/api/v1/admin/vendors.py
index e326af24..0f9e1439 100644
--- a/app/api/v1/admin/vendors.py
+++ b/app/api/v1/admin/vendors.py
@@ -20,7 +20,7 @@ from app.services.admin_service import admin_service
from app.services.stats_service import stats_service
from app.services.vendor_service import vendor_service
from models.database.user import User
-from models.schema.stats import VendorStatsResponse
+from app.modules.analytics.schemas import VendorStatsResponse
from models.schema.vendor import (
LetzshopExportRequest,
LetzshopExportResponse,
diff --git a/app/api/v1/vendor/analytics.py b/app/api/v1/vendor/analytics.py
index b3b9ca76..a4c79db3 100644
--- a/app/api/v1/vendor/analytics.py
+++ b/app/api/v1/vendor/analytics.py
@@ -21,7 +21,7 @@ from app.core.feature_gate import RequireFeature
from app.services.stats_service import stats_service
from models.database.feature import FeatureCode
from models.database.user import User
-from models.schema.stats import (
+from app.modules.analytics.schemas import (
VendorAnalyticsCatalog,
VendorAnalyticsImports,
VendorAnalyticsInventory,
diff --git a/app/api/v1/vendor/dashboard.py b/app/api/v1/vendor/dashboard.py
index 346a2a31..a2827d19 100644
--- a/app/api/v1/vendor/dashboard.py
+++ b/app/api/v1/vendor/dashboard.py
@@ -17,7 +17,7 @@ from app.exceptions import VendorNotActiveException
from app.services.stats_service import stats_service
from app.services.vendor_service import vendor_service
from models.database.user import User
-from models.schema.stats import (
+from app.modules.analytics.schemas import (
VendorCustomerStats,
VendorDashboardStatsResponse,
VendorInfo,
diff --git a/app/api/v1/vendor/notifications.py b/app/api/v1/vendor/notifications.py
index a6983fc1..c9c2545e 100644
--- a/app/api/v1/vendor/notifications.py
+++ b/app/api/v1/vendor/notifications.py
@@ -15,7 +15,7 @@ from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.services.vendor_service import vendor_service
from models.database.user import User
-from models.schema.notification import (
+from app.modules.messaging.schemas import (
MessageResponse,
NotificationListResponse,
NotificationSettingsResponse,
diff --git a/app/modules/customers/routes/api/storefront.py b/app/modules/customers/routes/api/storefront.py
index ae853c16..d6c3f442 100644
--- a/app/modules/customers/routes/api/storefront.py
+++ b/app/modules/customers/routes/api/storefront.py
@@ -31,7 +31,7 @@ from app.modules.customers.services import (
)
from app.services.auth_service import AuthService
from app.services.email_service import EmailService
-from models.database.password_reset_token import PasswordResetToken
+from app.modules.customers.models import PasswordResetToken
from models.schema.auth import (
LogoutResponse,
PasswordResetRequestResponse,
diff --git a/app/modules/monitoring/models/__init__.py b/app/modules/monitoring/models/__init__.py
index 3f0b5682..e7618e71 100644
--- a/app/modules/monitoring/models/__init__.py
+++ b/app/modules/monitoring/models/__init__.py
@@ -9,10 +9,8 @@ Re-exports monitoring-related models from their source locations.
from app.modules.billing.models import CapacitySnapshot
# Admin notification and logging models
-from models.database.admin import (
- AdminNotification,
- PlatformAlert,
-)
+from app.modules.messaging.models import AdminNotification
+from models.database.admin import PlatformAlert
__all__ = [
"CapacitySnapshot",
diff --git a/app/modules/orders/schemas/__init__.py b/app/modules/orders/schemas/__init__.py
index 8b9119f2..8876ff34 100644
--- a/app/modules/orders/schemas/__init__.py
+++ b/app/modules/orders/schemas/__init__.py
@@ -43,6 +43,18 @@ from app.modules.orders.schemas.order import (
ShippingLabelInfo,
)
+from app.modules.orders.schemas.order_item_exception import (
+ OrderItemExceptionResponse,
+ OrderItemExceptionBriefResponse,
+ OrderItemExceptionListResponse,
+ OrderItemExceptionStats,
+ ResolveExceptionRequest,
+ IgnoreExceptionRequest,
+ BulkResolveRequest,
+ BulkResolveResponse,
+ AutoMatchResult,
+)
+
from app.modules.orders.schemas.invoice import (
# Invoice settings schemas
VendorInvoiceSettingsCreate,
@@ -79,6 +91,16 @@ __all__ = [
"OrderItemCreate",
"OrderItemExceptionBrief",
"OrderItemResponse",
+ # Order item exception schemas
+ "OrderItemExceptionResponse",
+ "OrderItemExceptionBriefResponse",
+ "OrderItemExceptionListResponse",
+ "OrderItemExceptionStats",
+ "ResolveExceptionRequest",
+ "IgnoreExceptionRequest",
+ "BulkResolveRequest",
+ "BulkResolveResponse",
+ "AutoMatchResult",
# Customer schemas
"CustomerSnapshot",
"CustomerSnapshotResponse",
diff --git a/app/modules/orders/schemas/order_item_exception.py b/app/modules/orders/schemas/order_item_exception.py
new file mode 100644
index 00000000..19c7bd29
--- /dev/null
+++ b/app/modules/orders/schemas/order_item_exception.py
@@ -0,0 +1,173 @@
+# app/modules/orders/schemas/order_item_exception.py
+"""
+Pydantic schemas for order item exception management.
+
+Handles unmatched products during marketplace order imports.
+"""
+
+from datetime import datetime
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+# ============================================================================
+# Exception Response Schemas
+# ============================================================================
+
+
+class OrderItemExceptionResponse(BaseModel):
+ """Schema for order item exception response."""
+
+ model_config = ConfigDict(from_attributes=True)
+
+ id: int
+ order_item_id: int
+ vendor_id: int
+ vendor_name: str | None = None # For cross-vendor views
+
+ # Original data from marketplace
+ original_gtin: str | None
+ original_product_name: str | None
+ original_sku: str | None
+
+ # Exception classification
+ exception_type: str # product_not_found, gtin_mismatch, duplicate_gtin
+
+ # Resolution status
+ status: str # pending, resolved, ignored
+
+ # Resolution details
+ resolved_product_id: int | None
+ resolved_at: datetime | None
+ resolved_by: int | None
+ resolution_notes: str | None
+
+ # Timestamps
+ created_at: datetime
+ updated_at: datetime
+
+ # Nested order info (populated by service)
+ order_number: str | None = None
+ order_id: int | None = None
+ order_date: datetime | None = None
+ order_status: str | None = None
+
+ @property
+ def is_pending(self) -> bool:
+ """Check if exception is pending resolution."""
+ return self.status == "pending"
+
+ @property
+ def is_resolved(self) -> bool:
+ """Check if exception has been resolved."""
+ return self.status == "resolved"
+
+ @property
+ def is_ignored(self) -> bool:
+ """Check if exception has been ignored."""
+ return self.status == "ignored"
+
+
+class OrderItemExceptionBriefResponse(BaseModel):
+ """Brief exception info for embedding in order item responses."""
+
+ model_config = ConfigDict(from_attributes=True)
+
+ id: int
+ original_gtin: str | None
+ original_product_name: str | None
+ exception_type: str
+ status: str
+ resolved_product_id: int | None
+
+
+# ============================================================================
+# List/Stats Response Schemas
+# ============================================================================
+
+
+class OrderItemExceptionListResponse(BaseModel):
+ """Paginated list of exceptions."""
+
+ exceptions: list[OrderItemExceptionResponse]
+ total: int
+ skip: int
+ limit: int
+
+
+class OrderItemExceptionStats(BaseModel):
+ """Exception statistics for a vendor."""
+
+ pending: int = 0
+ resolved: int = 0
+ ignored: int = 0
+ total: int = 0
+
+ # Additional breakdown
+ orders_with_exceptions: int = 0
+
+
+# ============================================================================
+# Request Schemas
+# ============================================================================
+
+
+class ResolveExceptionRequest(BaseModel):
+ """Request to resolve an exception by assigning a product."""
+
+ product_id: int = Field(..., description="Product ID to assign to this order item")
+ notes: str | None = Field(
+ None,
+ max_length=1000,
+ description="Optional notes about the resolution"
+ )
+
+
+class IgnoreExceptionRequest(BaseModel):
+ """Request to ignore an exception (still blocks confirmation)."""
+
+ notes: str = Field(
+ ...,
+ min_length=1,
+ max_length=1000,
+ description="Reason for ignoring (required)"
+ )
+
+
+class BulkResolveRequest(BaseModel):
+ """Request to bulk resolve all pending exceptions for a GTIN."""
+
+ gtin: str = Field(
+ ...,
+ min_length=1,
+ max_length=50,
+ description="GTIN to match pending exceptions"
+ )
+ product_id: int = Field(..., description="Product ID to assign")
+ notes: str | None = Field(
+ None,
+ max_length=1000,
+ description="Optional notes about the resolution"
+ )
+
+
+class BulkResolveResponse(BaseModel):
+ """Response from bulk resolve operation."""
+
+ resolved_count: int
+ gtin: str
+ product_id: int
+
+
+# ============================================================================
+# Auto-Match Response Schemas
+# ============================================================================
+
+
+class AutoMatchResult(BaseModel):
+ """Result of auto-matching after product import."""
+
+ gtin: str
+ product_id: int
+ resolved_count: int
+ resolved_exception_ids: list[int]
diff --git a/docs/proposals/SESSION_NOTE_2026-01-28_module-config-migrations.md b/docs/proposals/SESSION_NOTE_2026-01-28_module-config-migrations.md
new file mode 100644
index 00000000..759fcbb9
--- /dev/null
+++ b/docs/proposals/SESSION_NOTE_2026-01-28_module-config-migrations.md
@@ -0,0 +1,161 @@
+# Session Note: Module Config & Migrations Infrastructure
+
+**Date:** 2026-01-28
+**Focus:** Self-contained module configuration and migrations auto-discovery
+
+## Summary
+
+Completed the infrastructure for fully self-contained modules with config and migrations auto-discovery. All modules now have placeholder files ready for the final migration phase.
+
+## What Was Done
+
+### 1. Module Config Auto-Discovery
+
+Created `app/modules/config.py` that auto-discovers module configurations:
+
+```python
+from app.modules.config import get_module_config
+
+marketplace_config = get_module_config("marketplace")
+```
+
+Each module now has a `config.py` placeholder:
+
+```python
+# app/modules/marketplace/config.py
+class MarketplaceConfig(BaseSettings):
+ model_config = {"env_prefix": "MARKETPLACE_"}
+
+config = MarketplaceConfig()
+```
+
+### 2. Module Migrations Directories
+
+Created `migrations/versions/` directories for all 11 self-contained modules:
+- analytics, billing, cms, customers, dev_tools
+- inventory, marketplace, messaging, monitoring, orders, payments
+
+Each with required `__init__.py` files for Alembic discovery.
+
+### 3. New Architecture Rules
+
+Added rules MOD-013 to MOD-015:
+
+| Rule | Description |
+|------|-------------|
+| MOD-013 | config.py should export `config` or `config_class` |
+| MOD-014 | Migrations must follow naming convention `{module}_{seq}_{desc}.py` |
+| MOD-015 | Migrations directory must have `__init__.py` files |
+
+### 4. Module Migration Commits
+
+Committed all pending module migration work:
+
+| Commit | Description |
+|--------|-------------|
+| `2466dfd` | feat: add module config and migrations auto-discovery infrastructure |
+| `bd2c99a` | feat: complete analytics module self-containment |
+| `f79e67d` | feat: complete billing module self-containment |
+| `b74d134` | feat: complete marketplace module self-containment |
+| `705d336` | feat: add self-contained structure to remaining modules |
+| `d987274` | feat: complete dev_tools module self-containment |
+| `37cf74c` | refactor: update registry and main.py for module auto-discovery |
+| `3ffa337` | refactor: convert legacy models/schemas to re-exports |
+| `bf871dc` | refactor: convert legacy services/tasks to re-exports |
+| `fbcf079` | chore: update API routers, validation, and docs |
+
+### 5. Documentation Updates
+
+- `docs/architecture/module-system.md` - Added Module Configuration and Module Migrations sections
+- `docs/development/creating-modules.md` - Added config.py pattern, updated migrations docs
+
+## Current Module Status
+
+| Module | definition.py | config.py | migrations/ | routes/api/ | routes/pages/ | locales/ | Status |
+|--------|--------------|-----------|-------------|-------------|---------------|----------|--------|
+| analytics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Complete** |
+| billing | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | Needs pages |
+| cms | ✅ | ✅ | ✅ | ✅ | ✅ | - | **Complete** |
+| customers | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | - | Needs routes |
+| dev_tools | ✅ | ✅ | ✅ | ✅ | - | - | **Complete** |
+| inventory | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | - | Needs routes |
+| marketplace | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | Needs pages |
+| messaging | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | - | Needs routes |
+| monitoring | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | - | Needs routes |
+| orders | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | - | Needs routes |
+| payments | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | - | Needs routes |
+| core | ✅ | - | - | - | - | - | Minimal |
+| tenancy | ✅ | - | - | - | - | - | Minimal |
+
+## Decisions Made
+
+1. **Config is environment-based**: Each module uses Pydantic Settings with `{MODULE}_` prefix
+2. **Migrations stay central for now**: Existing migrations remain in `alembic/versions/`, module directories are for future changes
+3. **Migration reorganization deferred**: Will move existing migrations to modules before production
+4. **Legacy files become re-exports**: Original files in `models/database/`, `models/schema/`, `app/services/` re-export from modules
+
+## Tomorrow's Tasks
+
+### Phase 1: Module Alignment Audit
+
+Run architecture validator and fix all modules to comply with rules:
+
+```bash
+python scripts/validate_architecture.py
+```
+
+Check each module for:
+- [ ] MOD-001: Required directories exist
+- [ ] MOD-002: Services contain actual code (not re-exports)
+- [ ] MOD-003: Schemas contain actual code (not re-exports)
+- [ ] MOD-004: Routes import from module, not legacy
+- [ ] MOD-005: Templates and static exist for UI modules
+- [ ] MOD-008: exceptions.py exists
+- [ ] MOD-010: Routes export `router` variable
+- [ ] MOD-011: Tasks have `__init__.py`
+
+### Phase 2: Complete Module Routes Migration
+
+Move remaining routes to self-contained structure:
+
+1. **customers** - Move routes from `app/api/v1/*/customers.py`
+2. **inventory** - Move routes from `app/api/v1/*/inventory.py`
+3. **messaging** - Move routes from `app/api/v1/*/messages.py`
+4. **monitoring** - Move routes from `app/api/v1/admin/monitoring.py`
+5. **orders** - Move routes from `app/api/v1/*/orders.py`
+6. **payments** - Move routes from `app/api/v1/*/payments.py`
+
+### Phase 3: Migration Reorganization (Pre-Production)
+
+Move existing migrations to module-specific directories:
+
+1. Identify which tables belong to which module
+2. Create baseline migrations in module directories
+3. Test migration chain works correctly
+4. Remove/reorganize central migrations
+
+### Phase 4: Validation & Testing
+
+1. Run full architecture validation
+2. Test all routes are accessible
+3. Test module enable/disable functionality
+4. Verify Alembic discovers all migrations
+
+## Files Changed
+
+### New Files
+- `app/modules/config.py` - Config auto-discovery
+- `app/modules/*/config.py` - Module config placeholders (11 files)
+- `app/modules/*/migrations/__init__.py` - Migration package markers (11 modules)
+- `app/modules/*/migrations/versions/__init__.py` - Version package markers (11 modules)
+- `.architecture-rules/module.yaml` - Module validation rules
+
+### Modified Files
+- `docs/architecture/module-system.md` - Added config/migrations sections
+- `docs/development/creating-modules.md` - Added config pattern, updated migrations
+
+## Related Documentation
+
+- [Module System Architecture](../architecture/module-system.md)
+- [Creating Modules Guide](../development/creating-modules.md)
+- [Module Migration Plan](module-migration-plan.md)
diff --git a/docs/proposals/temp-loyalty b/docs/proposals/temp-loyalty
new file mode 100644
index 00000000..c4406440
--- /dev/null
+++ b/docs/proposals/temp-loyalty
@@ -0,0 +1,229 @@
+│ Loyalty Platform & Module Implementation Plan │
+│ │
+│ Overview │
+│ │
+│ Create a Loyalty Module for Wizamart that provides stamp-based and points-based loyalty programs with Google Wallet and Apple Wallet integration. │
+│ │
+│ Entity Mapping │
+│ ┌────────────────────┬────────────────────┬──────────────────────────────────────────────────────────┐ │
+│ │ Loyalty Concept │ Wizamart Entity │ Notes │ │
+│ ├────────────────────┼────────────────────┼──────────────────────────────────────────────────────────┤ │
+│ │ Merchant │ Company │ Existing - legal business entity │ │
+│ ├────────────────────┼────────────────────┼──────────────────────────────────────────────────────────┤ │
+│ │ Store │ Vendor │ Existing - brand/location │ │
+│ ├────────────────────┼────────────────────┼──────────────────────────────────────────────────────────┤ │
+│ │ Customer │ Customer │ Existing - has vendor_id, total_spent, marketing_consent │ │
+│ ├────────────────────┼────────────────────┼──────────────────────────────────────────────────────────┤ │
+│ │ Pass Object │ LoyaltyCard │ NEW - links customer to vendor's program │ │
+│ ├────────────────────┼────────────────────┼──────────────────────────────────────────────────────────┤ │
+│ │ Stamp/Points Event │ LoyaltyTransaction │ NEW - records all operations │ │
+│ ├────────────────────┼────────────────────┼──────────────────────────────────────────────────────────┤ │
+│ │ Staff PIN │ StaffPin │ NEW - fraud prevention │ │
+│ └────────────────────┴────────────────────┴──────────────────────────────────────────────────────────┘ │
+│ Database Models │
+│ │
+│ Core Models (5 tables) │
+│ │
+│ 1. loyalty_programs - Vendor's program configuration │
+│ - vendor_id (unique FK) │
+│ - loyalty_type: stamps | points | hybrid │
+│ - Stamps config: stamps_target, stamps_reward_description │
+│ - Points config: points_per_euro, points_rewards (JSON) │
+│ - Anti-fraud: cooldown_minutes, max_daily_stamps, require_staff_pin │
+│ - Branding: card_name, card_color, logo_url │
+│ - Wallet IDs: google_issuer_id, apple_pass_type_id │
+│ 2. loyalty_cards - Customer's card (PassObject) │
+│ - customer_id, program_id, vendor_id │
+│ - card_number (unique), qr_code_data │
+│ - Stamps: stamp_count, total_stamps_earned, stamps_redeemed │
+│ - Points: points_balance, total_points_earned, points_redeemed │
+│ - Wallet: google_object_id, apple_serial_number, apple_auth_token │
+│ - Timestamps: last_stamp_at, last_points_at │
+│ 3. loyalty_transactions - All loyalty events │
+│ - card_id, vendor_id, staff_pin_id │
+│ - transaction_type: stamp_earned | stamp_redeemed | points_earned | points_redeemed │
+│ - stamps_delta, points_delta, purchase_amount_cents │
+│ - Metadata: ip_address, user_agent, notes │
+│ 4. staff_pins - Fraud prevention │
+│ - program_id, vendor_id │
+│ - name, pin_hash (bcrypt) │
+│ - failed_attempts, locked_until │
+│ 5. apple_device_registrations - Apple Wallet push │
+│ - card_id, device_library_identifier, push_token │
+│ │
+│ Module Structure │
+│ │
+│ app/modules/loyalty/ │
+│ ├── __init__.py │
+│ ├── definition.py # ModuleDefinition (requires: customers) │
+│ ├── config.py # LOYALTY_ env vars │
+│ ├── exceptions.py │
+│ ├── models/ │
+│ │ ├── loyalty_program.py │
+│ │ ├── loyalty_card.py │
+│ │ ├── loyalty_transaction.py │
+│ │ ├── staff_pin.py │
+│ │ └── apple_device.py │
+│ ├── schemas/ │
+│ │ ├── program.py │
+│ │ ├── card.py │
+│ │ ├── stamp.py │
+│ │ ├── points.py │
+│ │ └── pin.py │
+│ ├── services/ │
+│ │ ├── program_service.py # Program CRUD │
+│ │ ├── card_service.py # Card enrollment, lookup │
+│ │ ├── stamp_service.py # Stamp logic + anti-fraud │
+│ │ ├── points_service.py # Points logic │
+│ │ ├── pin_service.py # PIN validation │
+│ │ ├── wallet_service.py # Unified wallet abstraction │
+│ │ ├── google_wallet_service.py │
+│ │ └── apple_wallet_service.py │
+│ ├── routes/ │
+│ │ ├── api/ │
+│ │ │ ├── admin.py # Platform admin │
+│ │ │ ├── vendor.py # Vendor dashboard │
+│ │ │ └── public.py # Enrollment, Apple web service │
+│ │ └── pages/ │
+│ ├── tasks/ │
+│ │ ├── point_expiration.py │
+│ │ └── wallet_sync.py │
+│ ├── migrations/versions/ │
+│ ├── locales/ │
+│ └── templates/ │
+│ │
+│ Key API Endpoints │
+│ │
+│ Public (Customer) │
+│ │
+│ - POST /api/v1/loyalty/enroll/{vendor_code} - Enroll in program │
+│ - GET /api/v1/loyalty/passes/apple/{serial}.pkpass - Download Apple pass │
+│ - Apple Web Service endpoints for device registration/updates │
+│ │
+│ Vendor (Staff) │
+│ │
+│ - POST /api/v1/vendor/loyalty/stamp - Add stamp (requires PIN) │
+│ - POST /api/v1/vendor/loyalty/points - Add points from purchase │
+│ - POST /api/v1/vendor/loyalty/*/redeem - Redeem for reward │
+│ - GET /api/v1/vendor/loyalty/cards - List customer cards │
+│ - GET /api/v1/vendor/loyalty/pins - Manage staff PINs │
+│ - GET /api/v1/vendor/loyalty/stats - Dashboard analytics │
+│ │
+│ Admin │
+│ │
+│ - GET /api/v1/admin/loyalty/programs - List all programs │
+│ - GET /api/v1/admin/loyalty/stats - Platform-wide stats │
+│ │
+│ Anti-Fraud System │
+│ │
+│ 1. Staff PIN - Required for all stamp/points operations │
+│ 2. Cooldown - Configurable minutes between stamps (default: 15) │
+│ 3. Daily Limit - Max stamps per card per day (default: 5) │
+│ 4. PIN Lockout - Lock after 5 failed attempts for 30 minutes │
+│ 5. Audit Trail - All transactions logged with IP/user agent │
+│ │
+│ Wallet Integration │
+│ │
+│ Google Wallet │
+│ │
+│ - Create LoyaltyClass when program created │
+│ - Create LoyaltyObject when customer enrolls │
+│ - PATCH object on stamp/points change │
+│ - Generate JWT-based "Add to Wallet" URL │
+│ │
+│ Apple Wallet │
+│ │
+│ - Generate .pkpass file (pass.json + images + signature) │
+│ - Implement Apple Web Service for device registration │
+│ - Send push notification on updates → device fetches new pass │
+│ │
+│ Implementation Phases │
+│ │
+│ Phase 1: MVP (Target) │
+│ │
+│ 1. Core Infrastructure │
+│ - Module structure, definition, exceptions │
+│ - Database models and migrations │
+│ - Program service (CRUD) │
+│ - Card service (enrollment, lookup) │
+│ 2. Stamp Loyalty │
+│ - Staff PIN service with lockout │
+│ - Stamp service with anti-fraud │
+│ - Transaction logging │
+│ - Vendor API routes │
+│ 3. Points Loyalty │
+│ - Points service │
+│ - Purchase-to-points calculation │
+│ - Redemption flow │
+│ 4. Wallet Integration │
+│ - Google Wallet service │
+│ - Apple .pkpass generation │
+│ - Apple Web Service endpoints │
+│ 5. Dashboard │
+│ - Vendor stats endpoint │
+│ - Transaction history │
+│ - QR code generation │
+│ │
+│ Phase 2: Future Enhancements │
+│ │
+│ - Rewards catalog with configurable tiers │
+│ - Customer tiers (Bronze/Silver/Gold) │
+│ - Referral program │
+│ - Gamification (spin wheel, scratch cards) │
+│ - POS integration │
+│ │
+│ Files to Create │
+│ │
+│ app/modules/loyalty/ │
+│ ├── __init__.py │
+│ ├── definition.py │
+│ ├── config.py │
+│ ├── exceptions.py │
+│ ├── models/__init__.py │
+│ ├── models/loyalty_program.py │
+│ ├── models/loyalty_card.py │
+│ ├── models/loyalty_transaction.py │
+│ ├── models/staff_pin.py │
+│ ├── models/apple_device.py │
+│ ├── schemas/__init__.py │
+│ ├── schemas/program.py │
+│ ├── schemas/card.py │
+│ ├── schemas/stamp.py │
+│ ├── schemas/points.py │
+│ ├── schemas/pin.py │
+│ ├── services/__init__.py │
+│ ├── services/program_service.py │
+│ ├── services/card_service.py │
+│ ├── services/stamp_service.py │
+│ ├── services/points_service.py │
+│ ├── services/pin_service.py │
+│ ├── services/wallet_service.py │
+│ ├── services/google_wallet_service.py │
+│ ├── services/apple_wallet_service.py │
+│ ├── routes/__init__.py │
+│ ├── routes/api/__init__.py │
+│ ├── routes/api/admin.py │
+│ ├── routes/api/vendor.py │
+│ ├── routes/api/public.py │
+│ ├── routes/pages/__init__.py │
+│ ├── tasks/__init__.py │
+│ ├── tasks/point_expiration.py │
+│ ├── migrations/__init__.py │
+│ ├── migrations/versions/__init__.py │
+│ └── locales/{en,fr,de,lu}.json │
+│ │
+│ Reference Patterns │
+│ │
+│ - Module definition: app/modules/billing/definition.py │
+│ - Models: app/modules/billing/models/subscription.py │
+│ - Services: app/modules/billing/services/subscription_service.py │
+│ - Customer integration: models/database/customer.py │
+│ │
+│ Verification │
+│ │
+│ 1. Run architecture validator: python scripts/validate_architecture.py │
+│ 2. Run migrations: alembic upgrade head │
+│ 3. Test enrollment flow via API │
+│ 4. Test stamp/points operations with PIN │
+│ 5. Verify wallet pass generation │
+│ 6. Check anti-fraud (cooldown, limits, lockout)
\ No newline at end of file
diff --git a/loyatly-ideas.html b/loyatly-ideas.html
new file mode 100644
index 00000000..33f7eeed
--- /dev/null
+++ b/loyatly-ideas.html
@@ -0,0 +1,678 @@
+
+
+
+
+ Loyalty Platform Architecture – Google Wallet & Apple Wallet
+
+
+
+
+
+
+
+
+ 1. High-Level Overview
+
+ This document describes a modular loyalty platform that supports:
+
+
+ - Stamp-based loyalty (e.g., coffee shops, barbers)
+ - Points-based loyalty (e.g., clothing stores, restaurants)
+ - Google Wallet passes (JSON + API)
+ - Apple Wallet passes (
.pkpass files)
+ - Staff PIN system for fraud prevention
+ - QR-based flows for visits and purchases
+ - Email collection and basic CRM
+
+
+ The architecture is designed to be extensible: you can add tiers, referrals, coupons, gift cards, and more without redesigning the core.
+
+
+
+
+ 2. Backend Architecture
+
+ 2.1 Core Modules
+
+ - Auth & Merchant Management: merchants, stores, staff PINs, billing hooks.
+ - Customer & Pass Management: customers, pass objects, Wallet IDs.
+ - Loyalty Engine: stamp logic and points logic.
+ - QR Engine: static QR codes per store, scan endpoints.
+ - Dashboard API: analytics, customer lists, rewards, exports.
+ - Wallet Integrations: Google Wallet and Apple Wallet modules.
+
+
+ 2.2 Folder Structure
+ backend/
+ app/
+ main.py
+ config.py
+ dependencies.py
+
+ api/
+ merchants.py
+ stores.py
+ passes.py
+ stamps.py
+ points.py
+ qr.py
+ dashboard.py
+ apple_passes.py
+
+ core/
+ google_wallet.py
+ loyalty_engine/
+ stamps.py
+ points.py
+ qr_generator.py
+ apple_wallet/
+ __init__.py
+ signer.py
+ generator.py
+ templates/
+ loyalty_pass.json
+ images/
+ icon.png
+ logo.png
+ strip.png
+
+ models/
+ merchant.py
+ store.py
+ customer.py
+ pass_object.py
+ stamp_event.py
+ points_event.py
+ apple_device_registration.py
+
+ db/
+ base.py
+ session.py
+ migrations/
+
+ utils/
+ security.py
+ id_generator.py
+ validators.py
+
+ requirements.txt
+
+
+
+ 3. Data Model
+
+ 3.1 Merchant vs Store
+
+ Merchant = business entity (e.g., “CoffeeLux”)
+ Store = specific location/branch (e.g., “CoffeeLux Gare”).
+
+
+ Merchant
+
+ | Field | Type |
+ | id | UUID |
+ | name | string |
+ | email | string |
+ | password_hash | string |
+ | created_at | datetime |
+
+
+ Store
+
+ | Field | Type |
+ | id | UUID |
+ | merchant_id | FK → Merchant |
+ | name | string |
+ | qr_code_url | string |
+ | loyalty_type | "stamps" | "points" | "hybrid" |
+ | staff_pin_hash | string (hashed) |
+
+
+ Customer
+
+ | Field | Type |
+ | id | UUID |
+ | email | string (nullable) |
+ | phone | string (nullable) |
+ | email_consent | bool |
+ | email_consent_at | datetime (nullable) |
+ | created_at | datetime |
+
+
+ PassObject
+
+ | Field | Type |
+ | id | UUID |
+ | customer_id | FK → Customer |
+ | store_id | FK → Store |
+ | google_pass_id | string (nullable) |
+ | apple_serial | string (nullable) |
+ | apple_auth_token | string (nullable) |
+ | stamp_count | int |
+ | points_balance | int |
+ | reward_unlocked | bool |
+ | last_stamp_at | datetime (nullable) |
+ | last_points_at | datetime (nullable) |
+
+
+ StampEvent
+
+ | Field | Type |
+ | id | UUID |
+ | pass_id | FK → PassObject |
+ | store_id | FK → Store |
+ | timestamp | datetime |
+
+
+ PointsEvent
+
+ | Field | Type |
+ | id | UUID |
+ | pass_id | FK → PassObject |
+ | store_id | FK → Store |
+ | points_added | int |
+ | amount | decimal (optional) |
+ | timestamp | datetime |
+
+
+ AppleDeviceRegistration
+
+ | Field | Type |
+ | id | UUID |
+ | device_id | string |
+ | pass_serial | string |
+ | push_token | string |
+ | updated_at | datetime |
+
+
+
+
+ 4. Core Flows
+
+ 4.1 Creating a Pass (Onboarding)
+
+ - Customer scans a QR code or visits a link.
+ - Page asks for optional data:
+
+ - Name (optional)
+ - Email (optional, with consent checkbox)
+ - Phone (optional)
+
+
+ - Backend:
+
+ - Creates or finds Customer.
+ - Creates PassObject for the chosen store.
+ - Generates Google Wallet link and/or Apple Wallet
.pkpass.
+
+
+ - Customer taps “Add to Google Wallet” or “Add to Apple Wallet”.
+
+
+ 4.2 Stamping a Pass (Visits)
+ Endpoint: POST /stamp/{store_id}
+
+ - Customer scans store QR:
https://yourdomain.com/stamp/{store_id}.
+ - Page identifies the customer’s pass (via login, token, or Wallet ID).
+ - Staff enters a PIN on the customer’s device.
+ - Backend:
+
+ - Validates staff PIN.
+ - Checks cooldown (e.g., 1 stamp per X minutes).
+ - Increments
stamp_count.
+ - Logs a StampEvent.
+ - Updates Google Wallet and Apple Wallet passes.
+
+
+ - Customer sees updated stamp count.
+
+
+ 4.3 Adding Points (Purchases)
+ Endpoint: POST /points/{store_id}
+ {
+ "pass_id": "abc123",
+ "amount": 59.90,
+ "staff_pin": "4821"
+}
+
+ - Staff enters purchase amount and PIN.
+ - Backend:
+
+ - Validates staff PIN.
+ - Converts amount → points (e.g., €1 = 1 point).
+ - Updates
points_balance.
+ - Logs a PointsEvent.
+ - Updates Wallet passes.
+
+
+
+
+ 4.4 Reward Logic
+
+ Implemented in loyalty_engine/stamps.py and loyalty_engine/points.py.
+
+
+ - Stamps: reward after N stamps.
+ - Points: reward at thresholds (e.g., 100, 250, 500 points).
+ - Hybrid: store can use both stamps and points.
+
+
+
+
+ 5. Staff PIN System & Anti-Fraud
+
+ 5.1 Staff PIN Concept
+
+ Each store has one or more staff PINs. A stamp or points addition is only valid if a correct PIN is provided.
+
+ Example request:
+ POST /stamp/{store_id}
+{
+ "pass_id": "abc123",
+ "staff_pin": "4821"
+}
+
+ 5.2 Implementation
+
+ - Store table:
staff_pin_hash (hashed with bcrypt/argon2).
+ - Staff enters PIN on customer’s device after a purchase/visit.
+ - Backend compares provided PIN with hash.
+
+
+ 5.3 Additional Anti-Fraud Layers
+
+ - Cooldown: 1 stamp per X minutes per pass.
+ - Device fingerprinting: optional (IP, user agent, cookies).
+ - Reward state checks: don’t stamp a full card.
+ - Staff PIN rotation: merchants can change PIN periodically.
+ - Attempt limits: block after N wrong PIN attempts.
+
+
+
+ The QR code itself never directly grants a stamp or points. It only opens a URL; the backend decides whether to award anything.
+
+
+
+
+ 6. Email Collection & GDPR Considerations
+
+ 6.1 When to Collect Email
+
+ - During pass creation: best moment, right after QR scan.
+ - On reward redemption: “Want your next reward reminder by email?”
+ - On progress view: small banner: “Add your email to get reward reminders.”
+
+
+ 6.2 Data Model
+
+ Customer.email
+ Customer.email_consent
+ Customer.email_consent_at
+
+
+ 6.3 GDPR-Friendly Flow (EU/Luxembourg)
+
+ - Use an explicit checkbox:
+
“I agree to receive loyalty updates and promotions.”
+
+ - Do not pre-check the box.
+ - Store consent timestamp.
+ - Provide unsubscribe link in emails.
+
+
+ 6.4 Usage
+
+ - Reward notifications (“Your reward is ready”).
+ - Progress nudges (“You’re 1 stamp away”).
+ - Promotions and new collection announcements.
+ - Birthday offers (if DOB is collected).
+
+
+
+
+ 7. Features Beyond Stamps & Points
+
+ 7.1 Reward Types
+
+ - Tiered loyalty levels: Bronze, Silver, Gold based on spend or visits.
+ - Cashback / store credit: earn balance redeemable in-store.
+ - Birthday rewards: automatic bonuses around customer’s birthday.
+ - Referral rewards: invite friends, both get points or discounts.
+ - Hybrid models: stamps + points + referrals.
+
+
+ 7.2 Coupons, Vouchers, Gift Cards
+
+ - Digital coupons (percentage or fixed amount).
+ - Digital gift cards with balance.
+ - Time-limited offers (weekend-only, seasonal).
+
+
+ 7.3 Marketing & Communication
+
+ - Email campaigns.
+ - SMS campaigns.
+ - Wallet-based notifications (pass updates, messages).
+
+
+ 7.4 Analytics & CRM
+
+ - Visit frequency, average spend, redemption rates.
+ - Customer lifetime value.
+ - New vs returning customers.
+ - Store-level and staff-level performance.
+ - Basic CRM: customer profiles, segmentation.
+
+
+ 7.5 Advanced Options
+
+ - Receipt scanning with OCR.
+ - POS integration (automatic points and redemptions).
+ - Gamification (spin-the-wheel, scratch cards, challenges).
+ - Paid memberships / VIP subscriptions.
+
+
+
+
+ 8. Google Wallet Integration
+
+ 8.1 Core Concepts
+
+ - Google Wallet uses Passes API with JSON-based classes and objects.
+ - You define a loyalty class and create pass objects per customer.
+ - Updates are done via API calls (patching fields like points, stamps, messages).
+
+
+ 8.2 Required Setup
+
+ - Google Cloud project.
+ - Wallet API enabled.
+ - Service account with JSON key.
+ - Issuer ID and loyalty class definition.
+
+
+ 8.3 Backend Responsibilities
+
+ - Create pass objects for new customers.
+ - Store
google_pass_id in PassObject.
+ - Update pass when stamps/points change.
+ - Optionally send messages or promotions via pass updates.
+
+
+ 8.4 Example Endpoint Sketch
+ @router.post("/passes/create")
+async def create_pass(data: PassCreateRequest, db: Session = Depends(get_db)):
+ customer = get_or_create_customer(db, data)
+ pass_obj = get_or_create_pass(db, customer, data.store_id)
+
+ google_link = google_wallet.create_pass(pass_obj)
+ return {"add_to_wallet_url": google_link}
+
+
+
+ 9. Apple Wallet Integration
+
+ 9.1 Core Difference
+
+ Apple Wallet uses signed .pkpass files instead of a JSON API.
+ A .pkpass is a ZIP containing:
+
+
+ pass.json – pass data
+ - Images – logo, icon, strip
+ manifest.json – SHA-1 hashes of all files
+ signature – signed manifest using your certificate
+
+
+ 9.2 Requirements
+
+ - Apple Developer Program membership.
+ - Pass Type ID (e.g.,
pass.com.yourbrand.loyalty).
+ - Pass certificate (
.p12).
+ - Push notification certificate for Wallet updates.
+
+
+ 9.3 Pass Template (pass.json)
+ {
+ "formatVersion": 1,
+ "passTypeIdentifier": "pass.com.yourbrand.loyalty",
+ "teamIdentifier": "ABCDE12345",
+ "organizationName": "Your Brand",
+ "serialNumber": "{{serial}}",
+ "description": "Loyalty Card",
+ "logoText": "Your Brand",
+ "foregroundColor": "rgb(255,255,255)",
+ "backgroundColor": "rgb(0,0,0)",
+ "storeCard": {
+ "primaryFields": [
+ {
+ "key": "points",
+ "label": "Points",
+ "value": "{{points}}"
+ }
+ ],
+ "secondaryFields": [
+ {
+ "key": "stamps",
+ "label": "Stamps",
+ "value": "{{stamps}}"
+ }
+ ]
+ },
+ "barcode": {
+ "format": "PKBarcodeFormatQR",
+ "message": "{{pass_id}}",
+ "messageEncoding": "iso-8859-1"
+ },
+ "webServiceURL": "https://yourdomain.com/apple/updates",
+ "authenticationToken": "{{auth_token}}"
+}
+
+ 9.4 pkpass Generation Flow
+
+ - Load
loyalty_pass.json template.
+ - Inject dynamic values (serial, points, stamps, auth token, etc.).
+ - Add required images (icon, logo, strip).
+ - Create
manifest.json with SHA-1 hashes of all files.
+ - Sign manifest with your
.p12 certificate and Apple’s WWDR cert.
+ - Zip everything into a
.pkpass file.
+ - Return
.pkpass from endpoint GET /passes/apple/{pass_id}.
+
+
+ 9.5 Device Registration & Updates
+ Apple Wallet uses a push + pull model for updates:
+
+ - Customer adds pass → Apple sends device registration to your backend.
+ - You store device ID, pass serial, and push token in
AppleDeviceRegistration.
+ - When stamps/points change:
+
+ - You send a push notification to Apple using the push token.
+ - Apple notifies the device; Wallet calls your
webServiceURL.
+ - Device fetches updated pass data from
GET /apple/updates/{pass_type}/{serial}.
+
+
+
+
+ 9.6 Data Model Additions
+
+ PassObject.apple_serial
+ PassObject.apple_auth_token
+ AppleDeviceRegistration table for device + push tokens.
+
+
+ 9.7 What Stays the Same
+
+ - Merchant, Store, Customer, PassObject core structure.
+ - Stamp and points logic.
+ - QR flows and staff PIN system.
+ - Dashboard and analytics.
+
+
+
+
+ 10. Multi-Wallet Strategy
+
+ Your platform supports both Google Wallet and Apple Wallet by treating them as two output formats for the same underlying PassObject.
+
+
+ - Google: store
google_pass_id, update via API.
+ - Apple: store
apple_serial and apple_auth_token, update via push + web service.
+
+
+ Whenever stamps or points change, you:
+
+
+ - Patch the Google Wallet pass (if it exists).
+ - Trigger Apple Wallet update (if Apple pass exists).
+
+
+ The merchant and store don’t need to care about the technical differences—your backend abstracts it away.
+
+
+
+
+ 11. Deployment & Stack
+
+ - Backend: FastAPI (Python).
+ - Database: PostgreSQL (or MySQL), with migrations.
+ - Caching / rate limiting: Redis (optional but recommended).
+ - Web server: Nginx or similar reverse proxy.
+ - Containerization: Docker for reproducible deployments.
+ - Security: HTTPS via Cloudflare or Let’s Encrypt.
+ - Cloud: Any provider; Google Cloud is convenient for Wallet API.
+
+
+
+
+ 12. MVP vs Future Phases
+
+ 12.1 MVP Scope
+
+ - Merchant + Store management.
+ - Customer + PassObject models.
+ - Stamp-based loyalty with staff PIN.
+ - Points-based loyalty for clothing stores.
+ - Google Wallet integration.
+ - Apple Wallet integration (basic, without advanced segmentation).
+ - Basic dashboard: customers, stamps, points, rewards.
+ - Email collection with consent.
+
+
+ 12.2 Phase 2 Ideas
+
+ - Tiers (Bronze/Silver/Gold).
+ - Referrals and invite links.
+ - Coupons, vouchers, and gift cards.
+ - Gamification (spin-the-wheel, scratch cards).
+ - Receipt scanning and POS integration.
+ - Paid memberships / VIP programs.
+
+
+
+
+ 13. Closing Notes
+
+ The key design choice is that you do not redesign the system when adding:
+
+
+ - Points on top of stamps.
+ - Apple Wallet on top of Google Wallet.
+ - New reward types (tiers, referrals, coupons).
+
+
+ You extend the same core models and flows with additional modules and fields, keeping the architecture modular and future-proof.
+
+
+
+
+
diff --git a/main.py b/main.py
index 9cf6edbc..ea0d4e7d 100644
--- a/main.py
+++ b/main.py
@@ -64,6 +64,9 @@ from app.exceptions.handler import setup_exception_handlers
# Import page routers (legacy routes - will be migrated to modules)
from app.routes import admin_pages, platform_pages, storefront_pages, vendor_pages
+# Import CMS module admin pages
+from app.modules.cms.routes.pages.admin import router as cms_admin_pages
+
# Module route auto-discovery
from app.modules.routes import discover_module_routes, get_vendor_page_routes
from app.utils.i18n import get_jinja2_globals
diff --git a/models/__init__.py b/models/__init__.py
index 85fac465..a4c6d20f 100644
--- a/models/__init__.py
+++ b/models/__init__.py
@@ -6,13 +6,15 @@ from . import schema
# Database models (SQLAlchemy)
from .database.base import Base
-from .database.inventory import Inventory
from .database.marketplace_import_job import MarketplaceImportJob
from .database.marketplace_product import MarketplaceProduct
from .database.product import Product
from .database.user import User
from .database.vendor import Vendor
+# Module-based models
+from app.modules.inventory.models import Inventory
+
# Export database models for Alembic
__all__ = [
"Base",
diff --git a/models/database/__init__.py b/models/database/__init__.py
index 42848464..8b738f05 100644
--- a/models/database/__init__.py
+++ b/models/database/__init__.py
@@ -25,11 +25,11 @@ logger = logging.getLogger(__name__)
from .admin import (
AdminAuditLog,
- AdminNotification,
AdminSession,
AdminSetting,
PlatformAlert,
)
+from app.modules.messaging.models import AdminNotification
from .admin_menu_config import AdminMenuConfig, FrontendType, MANDATORY_MENU_ITEMS
from .admin_platform import AdminPlatform
from .architecture_scan import (
@@ -43,15 +43,15 @@ from .company import Company
from .platform import Platform
from .platform_module import PlatformModule
from .vendor_platform import VendorPlatform
-from .customer import Customer, CustomerAddress
-from .password_reset_token import PasswordResetToken
+from app.modules.customers.models import Customer, CustomerAddress
+from app.modules.customers.models import PasswordResetToken
from .email import EmailCategory, EmailLog, EmailStatus, EmailTemplate
from .vendor_email_template import VendorEmailTemplate
from .vendor_email_settings import EmailProvider, VendorEmailSettings, PREMIUM_EMAIL_PROVIDERS
from .feature import Feature, FeatureCategory, FeatureCode, FeatureUILocation
-from .inventory import Inventory
-from .inventory_transaction import InventoryTransaction, TransactionType
-from .invoice import (
+from app.modules.inventory.models import Inventory
+from app.modules.inventory.models import InventoryTransaction, TransactionType
+from app.modules.orders.models import (
Invoice,
InvoiceStatus,
VATRegime,
@@ -64,7 +64,7 @@ from .letzshop import (
VendorLetzshopCredentials,
)
from .marketplace_import_job import MarketplaceImportError, MarketplaceImportJob
-from .message import (
+from app.modules.messaging.models import (
Conversation,
ConversationParticipant,
ConversationType,
@@ -80,8 +80,8 @@ from .marketplace_product import (
from .media import MediaFile, ProductMedia
from .marketplace_product_translation import MarketplaceProductTranslation
from .onboarding import OnboardingStatus, OnboardingStep, VendorOnboarding
-from .order import Order, OrderItem
-from .order_item_exception import OrderItemException
+from app.modules.orders.models import Order, OrderItem
+from app.modules.orders.models import OrderItemException
from .product import Product
from .product_translation import ProductTranslation
from .subscription import (
diff --git a/models/database/admin.py b/models/database/admin.py
index d5c05d26..32f99d49 100644
--- a/models/database/admin.py
+++ b/models/database/admin.py
@@ -59,37 +59,8 @@ class AdminAuditLog(Base, TimestampMixin):
return f""
-class AdminNotification(Base, TimestampMixin):
- """
- Admin-specific notifications for system alerts and warnings.
-
- Different from vendor/customer notifications - these are for platform
- administrators to track system health and issues requiring attention.
- """
-
- __tablename__ = "admin_notifications"
-
- id = Column(Integer, primary_key=True, index=True)
- type = Column(
- String(50), nullable=False, index=True
- ) # system_alert, vendor_issue, import_failure
- priority = Column(
- String(20), default="normal", index=True
- ) # low, normal, high, critical
- title = Column(String(200), nullable=False)
- message = Column(Text, nullable=False)
- is_read = Column(Boolean, default=False, index=True)
- read_at = Column(DateTime, nullable=True)
- read_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
- action_required = Column(Boolean, default=False, index=True)
- action_url = Column(String(500)) # Link to relevant admin page
- notification_metadata = Column(JSON) # Additional contextual data
-
- # Relationships
- read_by = relationship("User", foreign_keys=[read_by_user_id])
-
- def __repr__(self):
- return f""
+# AdminNotification has been moved to app/modules/messaging/models/admin_notification.py
+# It's re-exported via models/database/__init__.py for backwards compatibility
class AdminSetting(Base, TimestampMixin):
diff --git a/models/schema/__init__.py b/models/schema/__init__.py
index cdb71410..94e9508c 100644
--- a/models/schema/__init__.py
+++ b/models/schema/__init__.py
@@ -1,18 +1,22 @@
# models/schema/__init__.py
-"""API models package - Pydantic models for request/response validation."""
+"""API models package - Pydantic models for request/response validation.
-# Import API model modules
+Note: Many schemas have been migrated to their respective modules:
+- Customer schemas: app.modules.customers.schemas
+- Order schemas: app.modules.orders.schemas
+- Inventory schemas: app.modules.inventory.schemas
+- Message schemas: app.modules.messaging.schemas
+- Cart schemas: app.modules.cart.schemas
+"""
+
+# Import API model modules that remain in legacy location
from . import (
auth,
base,
email,
- inventory,
- invoice,
marketplace_import_job,
marketplace_product,
- message,
onboarding,
- stats,
vendor,
)
@@ -23,12 +27,8 @@ __all__ = [
"base",
"auth",
"email",
- "invoice",
"marketplace_product",
- "message",
- "inventory",
"onboarding",
"vendor",
"marketplace_import_job",
- "stats",
]
diff --git a/tests/integration/api/v1/storefront/test_password_reset.py b/tests/integration/api/v1/storefront/test_password_reset.py
index abf3f9cf..894cfbc3 100644
--- a/tests/integration/api/v1/storefront/test_password_reset.py
+++ b/tests/integration/api/v1/storefront/test_password_reset.py
@@ -10,7 +10,7 @@ from unittest.mock import MagicMock, patch
import pytest
from app.modules.customers.models.customer import Customer
-from models.database.password_reset_token import PasswordResetToken
+from app.modules.customers.models import PasswordResetToken
@pytest.fixture
diff --git a/tests/unit/services/test_admin_notification_service.py b/tests/unit/services/test_admin_notification_service.py
index bb4f160d..19a71daa 100644
--- a/tests/unit/services/test_admin_notification_service.py
+++ b/tests/unit/services/test_admin_notification_service.py
@@ -15,7 +15,8 @@ from app.services.admin_notification_service import (
Priority,
Severity,
)
-from models.database.admin import AdminNotification, PlatformAlert
+from app.modules.messaging.models import AdminNotification
+from models.database.admin import PlatformAlert
@pytest.fixture