fix: resolve all remaining legacy import issues

- Update models/database/__init__.py to import from module locations
- Update models/schema/__init__.py to remove deleted modules
- Update models/__init__.py to import Inventory from module
- Remove duplicate AdminNotification from models/database/admin.py
- Fix monitoring module to import AdminNotification from messaging
- Update stats schema imports in admin/vendor API
- Update notification schema imports
- Add order_item_exception.py schema to orders module
- Fix app/api/v1/__init__.py to use storefront instead of shop
- Add cms_admin_pages import to main.py
- Fix password_reset_token imports
- Fix AdminNotification test imports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 09:21:29 +01:00
parent a55eb78c64
commit eeafe6389f
23 changed files with 1306 additions and 68 deletions

View File

@@ -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"]

View File

@@ -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()

View File

@@ -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,

View File

@@ -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__)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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",

View File

@@ -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",

View File

@@ -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]

View File

@@ -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)

229
docs/proposals/temp-loyalty Normal file
View File

@@ -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)

678
loyatly-ideas.html Normal file
View File

@@ -0,0 +1,678 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Loyalty Platform Architecture Google Wallet & Apple Wallet</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
background: #0b1020;
color: #f5f5f5;
}
header {
background: linear-gradient(135deg, #1f2937, #111827);
padding: 2rem 1.5rem;
border-bottom: 1px solid #374151;
}
header h1 {
margin: 0 0 0.5rem 0;
font-size: 1.8rem;
}
header p {
margin: 0;
color: #9ca3af;
}
main {
max-width: 960px;
margin: 0 auto;
padding: 1.5rem;
}
h2, h3, h4 {
color: #e5e7eb;
margin-top: 2rem;
}
h2 {
border-bottom: 1px solid #374151;
padding-bottom: 0.25rem;
}
code {
background: #111827;
padding: 0.15rem 0.35rem;
border-radius: 4px;
font-size: 0.9em;
}
pre {
background: #111827;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
font-size: 0.9em;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
font-size: 0.95em;
}
th, td {
border: 1px solid #374151;
padding: 0.5rem 0.75rem;
text-align: left;
}
th {
background: #111827;
}
a {
color: #60a5fa;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.tag {
display: inline-block;
padding: 0.1rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
background: #111827;
color: #9ca3af;
margin-right: 0.25rem;
}
.note {
border-left: 3px solid #3b82f6;
padding-left: 0.75rem;
margin: 1rem 0;
color: #d1d5db;
}
ul {
padding-left: 1.25rem;
}
</style>
</head>
<body>
<header>
<h1>Loyalty Platform Architecture</h1>
<p>Digital stamp & points system with Google Wallet and Apple Wallet support</p>
</header>
<main>
<section id="overview">
<h2>1. High-Level Overview</h2>
<p>
This document describes a modular loyalty platform that supports:
</p>
<ul>
<li><strong>Stamp-based loyalty</strong> (e.g., coffee shops, barbers)</li>
<li><strong>Points-based loyalty</strong> (e.g., clothing stores, restaurants)</li>
<li><strong>Google Wallet passes</strong> (JSON + API)</li>
<li><strong>Apple Wallet passes</strong> (<code>.pkpass</code> files)</li>
<li><strong>Staff PIN system</strong> for fraud prevention</li>
<li><strong>QR-based flows</strong> for visits and purchases</li>
<li><strong>Email collection</strong> and basic CRM</li>
</ul>
<p>
The architecture is designed to be extensible: you can add tiers, referrals, coupons, gift cards, and more without redesigning the core.
</p>
</section>
<section id="architecture">
<h2>2. Backend Architecture</h2>
<h3>2.1 Core Modules</h3>
<ul>
<li><strong>Auth & Merchant Management:</strong> merchants, stores, staff PINs, billing hooks.</li>
<li><strong>Customer & Pass Management:</strong> customers, pass objects, Wallet IDs.</li>
<li><strong>Loyalty Engine:</strong> stamp logic and points logic.</li>
<li><strong>QR Engine:</strong> static QR codes per store, scan endpoints.</li>
<li><strong>Dashboard API:</strong> analytics, customer lists, rewards, exports.</li>
<li><strong>Wallet Integrations:</strong> Google Wallet and Apple Wallet modules.</li>
</ul>
<h3>2.2 Folder Structure</h3>
<pre><code>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</code></pre>
</section>
<section id="data-model">
<h2>3. Data Model</h2>
<h3>3.1 Merchant vs Store</h3>
<p>
<span class="tag">Merchant</span> = business entity (e.g., “CoffeeLux”)
<span class="tag">Store</span> = specific location/branch (e.g., “CoffeeLux Gare”).
</p>
<h4>Merchant</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>name</td><td>string</td></tr>
<tr><td>email</td><td>string</td></tr>
<tr><td>password_hash</td><td>string</td></tr>
<tr><td>created_at</td><td>datetime</td></tr>
</table>
<h4>Store</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>merchant_id</td><td>FK → Merchant</td></tr>
<tr><td>name</td><td>string</td></tr>
<tr><td>qr_code_url</td><td>string</td></tr>
<tr><td>loyalty_type</td><td><code>"stamps"</code> | <code>"points"</code> | <code>"hybrid"</code></td></tr>
<tr><td>staff_pin_hash</td><td>string (hashed)</td></tr>
</table>
<h4>Customer</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>email</td><td>string (nullable)</td></tr>
<tr><td>phone</td><td>string (nullable)</td></tr>
<tr><td>email_consent</td><td>bool</td></tr>
<tr><td>email_consent_at</td><td>datetime (nullable)</td></tr>
<tr><td>created_at</td><td>datetime</td></tr>
</table>
<h4>PassObject</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>customer_id</td><td>FK → Customer</td></tr>
<tr><td>store_id</td><td>FK → Store</td></tr>
<tr><td>google_pass_id</td><td>string (nullable)</td></tr>
<tr><td>apple_serial</td><td>string (nullable)</td></tr>
<tr><td>apple_auth_token</td><td>string (nullable)</td></tr>
<tr><td>stamp_count</td><td>int</td></tr>
<tr><td>points_balance</td><td>int</td></tr>
<tr><td>reward_unlocked</td><td>bool</td></tr>
<tr><td>last_stamp_at</td><td>datetime (nullable)</td></tr>
<tr><td>last_points_at</td><td>datetime (nullable)</td></tr>
</table>
<h4>StampEvent</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>pass_id</td><td>FK → PassObject</td></tr>
<tr><td>store_id</td><td>FK → Store</td></tr>
<tr><td>timestamp</td><td>datetime</td></tr>
</table>
<h4>PointsEvent</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>pass_id</td><td>FK → PassObject</td></tr>
<tr><td>store_id</td><td>FK → Store</td></tr>
<tr><td>points_added</td><td>int</td></tr>
<tr><td>amount</td><td>decimal (optional)</td></tr>
<tr><td>timestamp</td><td>datetime</td></tr>
</table>
<h4>AppleDeviceRegistration</h4>
<table>
<tr><th>Field</th><th>Type</th></tr>
<tr><td>id</td><td>UUID</td></tr>
<tr><td>device_id</td><td>string</td></tr>
<tr><td>pass_serial</td><td>string</td></tr>
<tr><td>push_token</td><td>string</td></tr>
<tr><td>updated_at</td><td>datetime</td></tr>
</table>
</section>
<section id="flows">
<h2>4. Core Flows</h2>
<h3>4.1 Creating a Pass (Onboarding)</h3>
<ol>
<li>Customer scans a QR code or visits a link.</li>
<li>Page asks for optional data:
<ul>
<li>Name (optional)</li>
<li>Email (optional, with consent checkbox)</li>
<li>Phone (optional)</li>
</ul>
</li>
<li>Backend:
<ul>
<li>Creates or finds Customer.</li>
<li>Creates PassObject for the chosen store.</li>
<li>Generates Google Wallet link and/or Apple Wallet <code>.pkpass</code>.</li>
</ul>
</li>
<li>Customer taps “Add to Google Wallet” or “Add to Apple Wallet”.</li>
</ol>
<h3>4.2 Stamping a Pass (Visits)</h3>
<p><strong>Endpoint:</strong> <code>POST /stamp/{store_id}</code></p>
<ol>
<li>Customer scans store QR: <code>https://yourdomain.com/stamp/{store_id}</code>.</li>
<li>Page identifies the customers pass (via login, token, or Wallet ID).</li>
<li>Staff enters a PIN on the customers device.</li>
<li>Backend:
<ul>
<li>Validates staff PIN.</li>
<li>Checks cooldown (e.g., 1 stamp per X minutes).</li>
<li>Increments <code>stamp_count</code>.</li>
<li>Logs a StampEvent.</li>
<li>Updates Google Wallet and Apple Wallet passes.</li>
</ul>
</li>
<li>Customer sees updated stamp count.</li>
</ol>
<h3>4.3 Adding Points (Purchases)</h3>
<p><strong>Endpoint:</strong> <code>POST /points/{store_id}</code></p>
<pre><code>{
"pass_id": "abc123",
"amount": 59.90,
"staff_pin": "4821"
}</code></pre>
<ol>
<li>Staff enters purchase amount and PIN.</li>
<li>Backend:
<ul>
<li>Validates staff PIN.</li>
<li>Converts amount → points (e.g., €1 = 1 point).</li>
<li>Updates <code>points_balance</code>.</li>
<li>Logs a PointsEvent.</li>
<li>Updates Wallet passes.</li>
</ul>
</li>
</ol>
<h3>4.4 Reward Logic</h3>
<p>
Implemented in <code>loyalty_engine/stamps.py</code> and <code>loyalty_engine/points.py</code>.
</p>
<ul>
<li><strong>Stamps:</strong> reward after N stamps.</li>
<li><strong>Points:</strong> reward at thresholds (e.g., 100, 250, 500 points).</li>
<li><strong>Hybrid:</strong> store can use both stamps and points.</li>
</ul>
</section>
<section id="staff-pin">
<h2>5. Staff PIN System & Anti-Fraud</h2>
<h3>5.1 Staff PIN Concept</h3>
<p>
Each store has one or more staff PINs. A stamp or points addition is only valid if a correct PIN is provided.
</p>
<p><strong>Example request:</strong></p>
<pre><code>POST /stamp/{store_id}
{
"pass_id": "abc123",
"staff_pin": "4821"
}</code></pre>
<h3>5.2 Implementation</h3>
<ul>
<li><strong>Store table:</strong> <code>staff_pin_hash</code> (hashed with bcrypt/argon2).</li>
<li>Staff enters PIN on customers device after a purchase/visit.</li>
<li>Backend compares provided PIN with hash.</li>
</ul>
<h3>5.3 Additional Anti-Fraud Layers</h3>
<ul>
<li><strong>Cooldown:</strong> 1 stamp per X minutes per pass.</li>
<li><strong>Device fingerprinting:</strong> optional (IP, user agent, cookies).</li>
<li><strong>Reward state checks:</strong> dont stamp a full card.</li>
<li><strong>Staff PIN rotation:</strong> merchants can change PIN periodically.</li>
<li><strong>Attempt limits:</strong> block after N wrong PIN attempts.</li>
</ul>
<div class="note">
The QR code itself never directly grants a stamp or points. It only opens a URL; the backend decides whether to award anything.
</div>
</section>
<section id="email">
<h2>6. Email Collection & GDPR Considerations</h2>
<h3>6.1 When to Collect Email</h3>
<ul>
<li><strong>During pass creation:</strong> best moment, right after QR scan.</li>
<li><strong>On reward redemption:</strong> “Want your next reward reminder by email?”</li>
<li><strong>On progress view:</strong> small banner: “Add your email to get reward reminders.”</li>
</ul>
<h3>6.2 Data Model</h3>
<ul>
<li><code>Customer.email</code></li>
<li><code>Customer.email_consent</code></li>
<li><code>Customer.email_consent_at</code></li>
</ul>
<h3>6.3 GDPR-Friendly Flow (EU/Luxembourg)</h3>
<ul>
<li>Use an explicit checkbox:
<br><em>“I agree to receive loyalty updates and promotions.”</em>
</li>
<li>Do not pre-check the box.</li>
<li>Store consent timestamp.</li>
<li>Provide unsubscribe link in emails.</li>
</ul>
<h3>6.4 Usage</h3>
<ul>
<li>Reward notifications (“Your reward is ready”).</li>
<li>Progress nudges (“Youre 1 stamp away”).</li>
<li>Promotions and new collection announcements.</li>
<li>Birthday offers (if DOB is collected).</li>
</ul>
</section>
<section id="features">
<h2>7. Features Beyond Stamps & Points</h2>
<h3>7.1 Reward Types</h3>
<ul>
<li><strong>Tiered loyalty levels:</strong> Bronze, Silver, Gold based on spend or visits.</li>
<li><strong>Cashback / store credit:</strong> earn balance redeemable in-store.</li>
<li><strong>Birthday rewards:</strong> automatic bonuses around customers birthday.</li>
<li><strong>Referral rewards:</strong> invite friends, both get points or discounts.</li>
<li><strong>Hybrid models:</strong> stamps + points + referrals.</li>
</ul>
<h3>7.2 Coupons, Vouchers, Gift Cards</h3>
<ul>
<li>Digital coupons (percentage or fixed amount).</li>
<li>Digital gift cards with balance.</li>
<li>Time-limited offers (weekend-only, seasonal).</li>
</ul>
<h3>7.3 Marketing & Communication</h3>
<ul>
<li>Email campaigns.</li>
<li>SMS campaigns.</li>
<li>Wallet-based notifications (pass updates, messages).</li>
</ul>
<h3>7.4 Analytics & CRM</h3>
<ul>
<li>Visit frequency, average spend, redemption rates.</li>
<li>Customer lifetime value.</li>
<li>New vs returning customers.</li>
<li>Store-level and staff-level performance.</li>
<li>Basic CRM: customer profiles, segmentation.</li>
</ul>
<h3>7.5 Advanced Options</h3>
<ul>
<li>Receipt scanning with OCR.</li>
<li>POS integration (automatic points and redemptions).</li>
<li>Gamification (spin-the-wheel, scratch cards, challenges).</li>
<li>Paid memberships / VIP subscriptions.</li>
</ul>
</section>
<section id="google-wallet">
<h2>8. Google Wallet Integration</h2>
<h3>8.1 Core Concepts</h3>
<ul>
<li>Google Wallet uses <strong>Passes API</strong> with JSON-based classes and objects.</li>
<li>You define a loyalty class and create pass objects per customer.</li>
<li>Updates are done via API calls (patching fields like points, stamps, messages).</li>
</ul>
<h3>8.2 Required Setup</h3>
<ul>
<li>Google Cloud project.</li>
<li>Wallet API enabled.</li>
<li>Service account with JSON key.</li>
<li>Issuer ID and loyalty class definition.</li>
</ul>
<h3>8.3 Backend Responsibilities</h3>
<ul>
<li>Create pass objects for new customers.</li>
<li>Store <code>google_pass_id</code> in PassObject.</li>
<li>Update pass when stamps/points change.</li>
<li>Optionally send messages or promotions via pass updates.</li>
</ul>
<h3>8.4 Example Endpoint Sketch</h3>
<pre><code>@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}</code></pre>
</section>
<section id="apple-wallet">
<h2>9. Apple Wallet Integration</h2>
<h3>9.1 Core Difference</h3>
<p>
Apple Wallet uses <strong>signed <code>.pkpass</code> files</strong> instead of a JSON API.
A <code>.pkpass</code> is a ZIP containing:
</p>
<ul>
<li><code>pass.json</code> pass data</li>
<li>Images logo, icon, strip</li>
<li><code>manifest.json</code> SHA-1 hashes of all files</li>
<li><code>signature</code> signed manifest using your certificate</li>
</ul>
<h3>9.2 Requirements</h3>
<ul>
<li>Apple Developer Program membership.</li>
<li>Pass Type ID (e.g., <code>pass.com.yourbrand.loyalty</code>).</li>
<li>Pass certificate (<code>.p12</code>).</li>
<li>Push notification certificate for Wallet updates.</li>
</ul>
<h3>9.3 Pass Template (pass.json)</h3>
<pre><code>{
"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}}"
}</code></pre>
<h3>9.4 pkpass Generation Flow</h3>
<ol>
<li>Load <code>loyalty_pass.json</code> template.</li>
<li>Inject dynamic values (serial, points, stamps, auth token, etc.).</li>
<li>Add required images (icon, logo, strip).</li>
<li>Create <code>manifest.json</code> with SHA-1 hashes of all files.</li>
<li>Sign manifest with your <code>.p12</code> certificate and Apples WWDR cert.</li>
<li>Zip everything into a <code>.pkpass</code> file.</li>
<li>Return <code>.pkpass</code> from endpoint <code>GET /passes/apple/{pass_id}</code>.</li>
</ol>
<h3>9.5 Device Registration & Updates</h3>
<p>Apple Wallet uses a push + pull model for updates:</p>
<ol>
<li>Customer adds pass → Apple sends device registration to your backend.</li>
<li>You store device ID, pass serial, and push token in <code>AppleDeviceRegistration</code>.</li>
<li>When stamps/points change:
<ul>
<li>You send a push notification to Apple using the push token.</li>
<li>Apple notifies the device; Wallet calls your <code>webServiceURL</code>.</li>
<li>Device fetches updated pass data from <code>GET /apple/updates/{pass_type}/{serial}</code>.</li>
</ul>
</li>
</ol>
<h3>9.6 Data Model Additions</h3>
<ul>
<li><code>PassObject.apple_serial</code></li>
<li><code>PassObject.apple_auth_token</code></li>
<li><code>AppleDeviceRegistration</code> table for device + push tokens.</li>
</ul>
<h3>9.7 What Stays the Same</h3>
<ul>
<li>Merchant, Store, Customer, PassObject core structure.</li>
<li>Stamp and points logic.</li>
<li>QR flows and staff PIN system.</li>
<li>Dashboard and analytics.</li>
</ul>
</section>
<section id="multi-wallet">
<h2>10. Multi-Wallet Strategy</h2>
<p>
Your platform supports both Google Wallet and Apple Wallet by treating them as two output formats for the same underlying PassObject.
</p>
<ul>
<li><strong>Google:</strong> store <code>google_pass_id</code>, update via API.</li>
<li><strong>Apple:</strong> store <code>apple_serial</code> and <code>apple_auth_token</code>, update via push + web service.</li>
</ul>
<p>
Whenever stamps or points change, you:
</p>
<ul>
<li>Patch the Google Wallet pass (if it exists).</li>
<li>Trigger Apple Wallet update (if Apple pass exists).</li>
</ul>
<p>
The merchant and store dont need to care about the technical differences—your backend abstracts it away.
</p>
</section>
<section id="deployment">
<h2>11. Deployment & Stack</h2>
<ul>
<li><strong>Backend:</strong> FastAPI (Python).</li>
<li><strong>Database:</strong> PostgreSQL (or MySQL), with migrations.</li>
<li><strong>Caching / rate limiting:</strong> Redis (optional but recommended).</li>
<li><strong>Web server:</strong> Nginx or similar reverse proxy.</li>
<li><strong>Containerization:</strong> Docker for reproducible deployments.</li>
<li><strong>Security:</strong> HTTPS via Cloudflare or Lets Encrypt.</li>
<li><strong>Cloud:</strong> Any provider; Google Cloud is convenient for Wallet API.</li>
</ul>
</section>
<section id="mvp">
<h2>12. MVP vs Future Phases</h2>
<h3>12.1 MVP Scope</h3>
<ul>
<li>Merchant + Store management.</li>
<li>Customer + PassObject models.</li>
<li>Stamp-based loyalty with staff PIN.</li>
<li>Points-based loyalty for clothing stores.</li>
<li>Google Wallet integration.</li>
<li>Apple Wallet integration (basic, without advanced segmentation).</li>
<li>Basic dashboard: customers, stamps, points, rewards.</li>
<li>Email collection with consent.</li>
</ul>
<h3>12.2 Phase 2 Ideas</h3>
<ul>
<li>Tiers (Bronze/Silver/Gold).</li>
<li>Referrals and invite links.</li>
<li>Coupons, vouchers, and gift cards.</li>
<li>Gamification (spin-the-wheel, scratch cards).</li>
<li>Receipt scanning and POS integration.</li>
<li>Paid memberships / VIP programs.</li>
</ul>
</section>
<section id="closing">
<h2>13. Closing Notes</h2>
<p>
The key design choice is that you do <strong>not</strong> redesign the system when adding:
</p>
<ul>
<li>Points on top of stamps.</li>
<li>Apple Wallet on top of Google Wallet.</li>
<li>New reward types (tiers, referrals, coupons).</li>
</ul>
<p>
You extend the same core models and flows with additional modules and fields, keeping the architecture modular and future-proof.
</p>
</section>
</main>
</body>
</html>

View File

@@ -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

View File

@@ -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",

View File

@@ -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 (

View File

@@ -59,37 +59,8 @@ class AdminAuditLog(Base, TimestampMixin):
return f"<AdminAuditLog(id={self.id}, action='{self.action}', target={self.target_type}:{self.target_id})>"
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(id={self.id}, type='{self.type}', priority='{self.priority}')>"
# 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):

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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