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

332 lines
10 KiB
Markdown

# 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
```python
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
```python
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
```python
# 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
```python
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:
```python
# 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:
```python
# 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:
```python
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
## Related Documentation
- [Multi-Tenant Architecture](multi-tenant.md)
- [Authentication & RBAC](auth-rbac.md)
- [Models Structure](models-structure.md)
- [Admin Integration Guide](../backend/admin-integration-guide.md)