Files
orion/docs/architecture/merchant-store-management.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

10 KiB

Merchant-Store Management Architecture

Overview

The Orion platform implements a hierarchical multi-tenant architecture where Merchants are the primary business entities and Stores are storefronts/brands that operate under merchants.

Merchant (Business Entity)
├── Owner (User)
├── Contact Information
├── Business Details
└── Stores (Storefronts/Brands)
    ├── Store 1
    ├── Store 2
    └── Store N

Key Concepts

Merchant

A Merchant represents a business entity on the platform. It contains:

  • Owner: A user account that has full control over the merchant
  • Contact Information: Business email, phone, website
  • Business Details: Address, tax number
  • Status: Active/Inactive, Verified/Pending

Store

A Store (also called storefront or brand) represents a specific storefront operating under a merchant. A merchant can have multiple stores.

  • Unique Identity: Store code and subdomain
  • Marketplace Integration: CSV URLs for product feeds (FR, EN, DE)
  • Status: Active/Inactive, Verified/Pending
  • Products: Each store has its own product catalog

Data Model

Merchant Model

class Merchant:
    id: int
    name: str
    description: str | None

    # Owner (at merchant level)
    owner_user_id: int  # FK to User

    # Contact Information
    contact_email: str
    contact_phone: str | None
    website: str | None

    # Business Details
    business_address: str | None
    tax_number: str | None

    # Status
    is_active: bool
    is_verified: bool

    # Timestamps
    created_at: datetime
    updated_at: datetime

    # Relationships
    owner: User  # Merchant owner
    stores: list[Store]  # Storefronts under this merchant

Store Model

class Store:
    id: int
    merchant_id: int  # FK to Merchant
    store_code: str  # Unique identifier (e.g., "TECHSTORE")
    subdomain: str  # URL subdomain (e.g., "tech-store")
    name: str
    description: str | None

    # Marketplace URLs (brand-specific)
    letzshop_csv_url_fr: str | None
    letzshop_csv_url_en: str | None
    letzshop_csv_url_de: str | None

    # Status
    is_active: bool
    is_verified: bool

    # Timestamps
    created_at: datetime
    updated_at: datetime

    # Relationships
    merchant: Merchant  # Parent merchant (owner is accessed via merchant.owner)

Key Design Decisions

1. Owner at Merchant Level Only

Previously, each store had its own owner_user_id. This has been refactored:

  • Before: Store.owner_user_id - each store had a separate owner
  • After: Merchant.owner_user_id - ownership is at merchant level

Rationale: A business entity (merchant) should have a single owner who can manage all storefronts. This simplifies:

  • User management
  • Permission handling
  • Ownership transfer operations

2. Contact Information at Merchant Level

Business contact information (email, phone, website, address, tax number) is now stored at the merchant level:

  • Before: Stores had contact fields
  • After: Contact info on Merchant model, stores reference parent merchant

Rationale: Business details are typically the same across all storefronts of a merchant.

3. Clean Architecture

The Store.owner_user_id field has been completely removed:

  • Ownership is determined solely via the merchant relationship
  • Use store.merchant.owner_user_id to get the owner
  • Use store.merchant.owner to get the owner User object

API Endpoints

Merchant Management (Admin)

Method Endpoint Description
POST /api/v1/admin/merchants Create merchant with owner
GET /api/v1/admin/merchants List all merchants
GET /api/v1/admin/merchants/{id} Get merchant details
PUT /api/v1/admin/merchants/{id} Update merchant
PUT /api/v1/admin/merchants/{id}/verification Toggle verification
PUT /api/v1/admin/merchants/{id}/status Toggle active status
POST /api/v1/admin/merchants/{id}/transfer-ownership Transfer ownership
DELETE /api/v1/admin/merchants/{id} Delete merchant

Store Management (Admin)

Method Endpoint Description
POST /api/v1/admin/stores Create store under merchant
GET /api/v1/admin/stores List all stores
GET /api/v1/admin/stores/{id} Get store details
PUT /api/v1/admin/stores/{id} Update store
PUT /api/v1/admin/stores/{id}/verification Toggle verification
PUT /api/v1/admin/stores/{id}/status Toggle active status
DELETE /api/v1/admin/stores/{id} Delete store

User Management (Admin)

Method Endpoint Description
GET /api/v1/admin/users List all users
GET /api/v1/admin/users/search?q={query} Search users by name/email
PUT /api/v1/admin/users/{id}/status Toggle user status
GET /api/v1/admin/users/stats Get user statistics

Service Layer

MerchantService (app/services/merchant_service.py)

Primary service for merchant operations:

  • create_merchant_with_owner() - Creates merchant and owner user account
  • get_merchant_by_id() - Get merchant with stores loaded
  • get_merchants() - Paginated list with filtering
  • update_merchant() - Update merchant fields
  • toggle_verification() - Verify/unverify merchant
  • toggle_active() - Activate/deactivate merchant
  • transfer_ownership() - Transfer to new owner
  • delete_merchant() - Delete merchant (requires no stores)

AdminService (app/services/admin_service.py)

Admin-specific store operations:

  • create_store() - Create store under existing merchant
  • get_all_stores() - Paginated list with merchant relationship
  • update_store() - Update store fields
  • verify_store() - Toggle verification
  • toggle_store_status() - Toggle active status
  • delete_store() - Delete store

StoreService (app/services/store_service.py)

Self-service store operations (for merchant owners):

  • create_store() - Create store (requires merchant_id, validates ownership)
  • get_stores() - Get stores with access control
  • get_store_by_code() - Get single store

Creating a New Merchant with Stores

Via Admin API

# 1. Create merchant with owner
POST /api/v1/admin/merchants
{
    "name": "Tech Solutions Ltd",
    "owner_email": "owner@techsolutions.com",
    "contact_email": "info@techsolutions.com",
    "contact_phone": "+352 123 456",
    "website": "https://techsolutions.com",
    "business_address": "123 Tech Street, Luxembourg",
    "tax_number": "LU12345678"
}

# Response includes temporary password for owner

# 2. Create store under merchant
POST /api/v1/admin/stores
{
    "merchant_id": 1,
    "store_code": "TECHSTORE",
    "subdomain": "tech-store",
    "name": "Tech Store",
    "description": "Consumer electronics storefront"
}

Ownership Transfer

Ownership transfer is a critical operation available at the merchant level:

  1. Admin initiates transfer via /api/v1/admin/merchants/{id}/transfer-ownership
  2. Merchant owner changes - Merchant.owner_user_id updated
  3. All stores affected - Stores inherit new owner via merchant relationship
  4. Audit trail - Transfer reason logged
POST /api/v1/admin/merchants/1/transfer-ownership
{
    "new_owner_user_id": 42,
    "confirm_transfer": true,
    "transfer_reason": "Business acquisition"
}

Admin UI Pages

Merchant List Page (/admin/merchants)

  • Lists all merchants with owner, store count, and status
  • Actions: View, Edit, Delete (disabled if merchant has stores)
  • Stats cards showing total, verified, active merchants

Merchant Detail Page (/admin/merchants/{id})

  • Quick Actions - Edit Merchant, Delete Merchant
  • Status Cards - Verification, Active status, Store count, Created date
  • Information Sections - Basic info, Contact info, Business details, Owner info
  • Stores Table - Lists all stores under this merchant with links
  • More Actions - Create Store button

Merchant Edit Page (/admin/merchants/{id}/edit)

  • Quick Actions - Verify/Unverify, Activate/Deactivate
  • Edit Form - Merchant details, contact info, business details
  • Statistics - Store counts (readonly)
  • More Actions - Transfer Ownership, Delete Merchant

Store Detail Page (/admin/stores/{code})

  • Quick Actions - Edit Store, Delete Store
  • Status Cards - Verification, Active status, Created/Updated dates
  • Information Sections - Basic info, Contact info, Business details, Owner info
  • More Actions - View Parent Merchant link, Customize Theme

Transfer Ownership Modal

A modal dialog for transferring merchant ownership:

  • User Search - Autocomplete search by username or email
  • Selected User Display - Shows selected user with option to clear
  • Transfer Reason - Optional field for audit trail
  • Confirmation Checkbox - Required before transfer
  • Validation - Inline error messages for missing fields

Best Practices

Creating Stores

Always use merchant_id when creating stores:

# Correct
store_data = StoreCreate(
    merchant_id=merchant.id,
    store_code="MYSTORE",
    subdomain="my-store",
    name="My Store"
)

# Incorrect (old pattern - don't use)
store_data = StoreCreate(
    owner_email="owner@example.com",  # No longer supported
    ...
)

Accessing Owner Information

Use the merchant relationship:

# Get owner User object
owner = store.merchant.owner

# Get owner details
owner_email = store.merchant.owner.email
owner_id = store.merchant.owner_user_id

Checking Permissions

For store operations, check merchant ownership:

def can_manage_store(user, store):
    if user.role == "admin":
        return True
    return store.merchant.owner_user_id == user.id

Migration Notes

The Store.owner_user_id column has been removed. If you have an existing database:

  1. Run the migration: alembic upgrade head
  2. This will drop the owner_user_id column from stores table
  3. Ownership is now determined via store.merchant.owner_user_id

If migrating from an older store-centric model:

  1. Create merchants for existing stores (group by owner)
  2. Assign stores to merchants via merchant_id
  3. Copy contact info from stores to merchants
  4. Run the migration to drop the old owner_user_id column