docs: migrate module documentation to single source of truth

Move 39 documentation files from top-level docs/ into each module's
docs/ folder, accessible via symlinks from docs/modules/. Create
data-model.md files for 10 modules with full schema documentation.
Replace originals with redirect stubs. Remove empty guide stubs.

Modules migrated: tenancy, billing, loyalty, marketplace, orders,
messaging, cms, catalog, inventory, hosting, prospecting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 23:38:37 +01:00
parent 2287f4597d
commit f141cc4e6a
140 changed files with 19921 additions and 17723 deletions

View File

@@ -1,254 +1 @@
# Email Settings Guide
This guide covers email configuration for both **stores** and **platform administrators**. The Orion platform uses a layered email system where stores manage their own email sending while the platform handles system-level communications.
## Overview
The email system has two distinct configurations:
| Aspect | Platform (Admin) | Store |
|--------|-----------------|--------|
| Purpose | System emails (billing, admin notifications) | Customer-facing emails (orders, marketing) |
| Configuration | Environment variables (.env) + Database overrides | Database (per-store) |
| Cost | Platform owner pays | Store pays |
| Providers | SMTP, SendGrid, Mailgun, SES | SMTP (all tiers), Premium providers (Business+) |
---
## Store Email Settings
### Getting Started
As a store, you need to configure email settings to send emails to your customers. This includes order confirmations, shipping updates, and marketing emails.
#### Accessing Email Settings
1. Log in to your Store Dashboard
2. Navigate to **Settings** from the sidebar
3. Click on the **Email** tab
### Available Providers
| Provider | Tier Required | Best For |
|----------|---------------|----------|
| SMTP | All tiers | Standard email servers, most common |
| SendGrid | Business+ | High-volume transactional emails |
| Mailgun | Business+ | Developer-friendly API |
| Amazon SES | Business+ | AWS ecosystem, cost-effective |
### Configuring SMTP
SMTP is available for all subscription tiers. Common SMTP providers include:
- Gmail (smtp.gmail.com:587)
- Microsoft 365 (smtp.office365.com:587)
- Your hosting provider's SMTP server
**Required Fields:**
- **From Email**: The sender email address (e.g., orders@yourstore.com)
- **From Name**: The sender display name (e.g., "Your Store")
- **SMTP Host**: Your SMTP server address
- **SMTP Port**: Usually 587 (TLS) or 465 (SSL)
- **SMTP Username**: Your login username
- **SMTP Password**: Your login password
- **Use TLS**: Enable for port 587 (recommended)
- **Use SSL**: Enable for port 465
### Configuring Premium Providers (Business+)
If you have a Business or Enterprise subscription, you can use premium email providers:
#### SendGrid
1. Create a SendGrid account at [sendgrid.com](https://sendgrid.com)
2. Generate an API key
3. Enter the API key in your store settings
#### Mailgun
1. Create a Mailgun account at [mailgun.com](https://mailgun.com)
2. Add and verify your domain
3. Get your API key from the dashboard
4. Enter the API key and domain in your settings
#### Amazon SES
1. Set up SES in your AWS account
2. Verify your sender domain/email
3. Create IAM credentials with SES permissions
4. Enter the access key, secret key, and region
### Verifying Your Configuration
After configuring your email settings:
1. Click **Save Settings**
2. Enter a test email address in the **Test Email** field
3. Click **Send Test**
4. Check your inbox for the test email
If the test fails, check:
- Your credentials are correct
- Your IP/domain is not blocked
- For Gmail: Allow "less secure apps" or use an app password
### Email Warning Banner
Until you configure and verify your email settings, you'll see a warning banner at the top of your dashboard. This ensures you don't forget to set up email before your store goes live.
---
## Platform Admin Email Settings
### Overview
Platform administrators can configure system-wide email settings for platform communications like:
- Subscription billing notifications
- Admin alerts
- Platform-wide announcements
### Configuration Sources
Admin email settings support two configuration sources:
1. **Environment Variables (.env)** - Default configuration
2. **Database Overrides** - Override .env via the admin UI
Database settings take priority over .env values.
### Accessing Admin Email Settings
1. Log in to the Admin Panel
2. Navigate to **Settings**
3. Click on the **Email** tab
### Viewing Current Configuration
The Email tab shows:
- **Provider**: Current email provider (SMTP, SendGrid, etc.)
- **From Email**: Sender email address
- **From Name**: Sender display name
- **Status**: Whether email is configured and enabled
- **DB Overrides**: Whether database overrides are active
### Editing Settings
Click **Edit Settings** to modify the email configuration:
1. Select the email provider
2. Enter the required credentials
3. Configure enabled/debug flags
4. Click **Save Email Settings**
### Resetting to .env Defaults
If you've made database overrides and want to revert to .env configuration:
1. Click **Reset to .env Defaults**
2. Confirm the action
This removes all email settings from the database, reverting to .env values.
### Testing Configuration
1. Enter a test email address
2. Click **Send Test**
3. Check your inbox
---
## Environment Variables Reference
For platform configuration via .env:
```env
# Provider: smtp, sendgrid, mailgun, ses
EMAIL_PROVIDER=smtp
# Sender identity
EMAIL_FROM_ADDRESS=noreply@yourplatform.com
EMAIL_FROM_NAME=Your Platform
EMAIL_REPLY_TO=support@yourplatform.com
# Behavior
EMAIL_ENABLED=true
EMAIL_DEBUG=false
# SMTP Configuration
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-username
SMTP_PASSWORD=your-password
SMTP_USE_TLS=true
SMTP_USE_SSL=false
# SendGrid
SENDGRID_API_KEY=your-api-key
# Mailgun
MAILGUN_API_KEY=your-api-key
MAILGUN_DOMAIN=mg.yourdomain.com
# Amazon SES
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=eu-west-1
```
---
## Tier-Based Branding
The email system includes tier-based branding for store emails:
| Tier | Branding |
|------|----------|
| Essential | "Powered by Orion" footer |
| Professional | "Powered by Orion" footer |
| Business | No branding (white-label) |
| Enterprise | No branding (white-label) |
Business and Enterprise tier stores get completely white-labeled emails with no Orion branding.
---
## Troubleshooting
### Common Issues
**"Email sending is disabled"**
- Check that `EMAIL_ENABLED=true` in .env
- Or enable it in the admin settings
**"Connection refused" on SMTP**
- Verify SMTP host and port
- Check firewall rules
- Ensure TLS/SSL settings match your server
**"Authentication failed"**
- Double-check username/password
- For Gmail, use an App Password
- For Microsoft 365, check MFA requirements
**"SendGrid error: 403"**
- Verify your API key has Mail Send permissions
- Check sender identity is verified
**Premium provider not available**
- Upgrade to Business or Enterprise tier
- Contact support if you have the right tier but can't access
### Debug Mode
Enable debug mode to log emails instead of sending them:
- Set `EMAIL_DEBUG=true` in .env
- Or enable "Debug mode" in admin settings
Debug mode logs the email content to the server logs without actually sending.
---
## Security Best Practices
1. **Never share API keys or passwords** in logs or frontend
2. **Use environment variables** for sensitive credentials
3. **Enable TLS** for SMTP connections
4. **Verify sender domains** with your email provider
5. **Monitor email logs** for delivery issues
6. **Rotate credentials** periodically
This document has moved to the messaging module docs: [Email Settings](../modules/messaging/email-settings.md)

View File

@@ -1,287 +1 @@
# Email Templates Guide
## Overview
The Orion platform provides a comprehensive email template system that allows:
- **Platform Administrators**: Manage all email templates across the platform
- **Stores**: Customize customer-facing emails with their own branding
This guide covers how to use the email template system from both perspectives.
---
## For Stores
### Accessing Email Templates
1. Log in to your store dashboard
2. Navigate to **Settings** > **Email Templates** in the sidebar
3. You'll see a list of all customizable email templates
### Understanding Template Status
Each template shows its customization status:
| Status | Description |
|--------|-------------|
| **Platform Default** | Using the standard Orion template |
| **Customized** | You have created a custom version |
| Language badges (green) | Languages where you have customizations |
### Customizing a Template
1. Click on any template to open the edit modal
2. Select the language tab you want to customize (EN, FR, DE, LB)
3. Edit the following fields:
- **Subject**: The email subject line
- **HTML Body**: The rich HTML content
- **Plain Text Body**: Fallback for email clients that don't support HTML
4. Click **Save** to save your customization
### Template Variables
Templates use special variables that are automatically replaced with actual values. Common variables include:
| Variable | Description |
|----------|-------------|
| `{{ customer_name }}` | Customer's first name |
| `{{ order_number }}` | Order reference number |
| `{{ store_name }}` | Your store name |
| `{{ platform_name }}` | Platform name (Orion or your whitelabel name) |
Each template shows its available variables in the reference panel.
### Previewing Templates
Before saving, you can preview your template:
1. Click **Preview** in the edit modal
2. A preview window shows how the email will look
3. Sample data is used for all variables
### Testing Templates
To send a test email:
1. Click **Send Test Email** in the edit modal
2. Enter your email address
3. Click **Send**
4. Check your inbox to see the actual email
### Reverting to Platform Default
If you want to remove your customization and use the platform default:
1. Open the template edit modal
2. Click **Revert to Default**
3. Confirm the action
Your customization will be deleted and the platform template will be used.
### Available Templates for Stores
| Template | Category | Description |
|----------|----------|-------------|
| Welcome Email | AUTH | Sent when a customer registers |
| Password Reset | AUTH | Password reset link |
| Order Confirmation | ORDERS | Sent after order placement |
| Shipping Notification | ORDERS | Sent when order is shipped |
**Note:** Billing and subscription emails are platform-only and cannot be customized.
---
## For Platform Administrators
### Accessing Email Templates
1. Log in to the admin dashboard
2. Navigate to **System** > **Email Templates** in the sidebar
3. You'll see all platform templates grouped by category
### Template Categories
| Category | Description | Store Override |
|----------|-------------|-----------------|
| AUTH | Authentication emails | Allowed |
| ORDERS | Order-related emails | Allowed |
| BILLING | Subscription/payment emails | **Not Allowed** |
| SYSTEM | System notifications | Allowed |
| MARKETING | Promotional emails | Allowed |
### Editing Platform Templates
1. Click on any template to open the edit modal
2. Select the language tab (EN, FR, DE, LB)
3. Edit the subject and body content
4. Click **Save**
**Important:** Changes to platform templates affect:
- All stores who haven't customized the template
- New stores automatically
### Creating New Templates
To add a new template:
1. Use the database seed script or migration
2. Define the template code, category, and languages
3. Set `is_platform_only` if stores shouldn't override it
### Viewing Email Logs
To see email delivery history:
1. Open a template
2. Click **View Logs**
3. See recent emails sent using this template
Logs show:
- Recipient email
- Send date/time
- Delivery status
- Store (if applicable)
### Template Best Practices
1. **Use all 4 languages**: Provide content in EN, FR, DE, and LB
2. **Test before publishing**: Always send test emails
3. **Include plain text**: Not all email clients support HTML
4. **Use consistent branding**: Follow Orion brand guidelines
5. **Keep subjects short**: Under 60 characters for mobile
---
## Language Resolution
When sending an email, the system determines the language in this order:
1. **Customer's preferred language** (if set in their profile)
2. **Store's storefront language** (if customer doesn't have preference)
3. **Platform default** (French - "fr")
### Template Resolution for Stores
1. System checks if store has a custom override
2. If yes, uses store's template
3. If no, falls back to platform template
4. If requested language unavailable, falls back to English
---
## Branding
### Standard Stores
Standard stores' emails include Orion branding:
- Orion logo in header
- "Powered by Orion" footer
### Whitelabel Stores
Enterprise-tier stores with whitelabel enabled:
- No Orion branding
- Store's logo in header
- Custom footer (if configured)
---
## Email Template Variables Reference
### Authentication Templates
#### signup_welcome
```
{{ first_name }} - Customer's first name
{{ merchant_name }} - Store merchant name
{{ email }} - Customer's email
{{ login_url }} - Link to login page
{{ trial_days }} - Trial period length
{{ tier_name }} - Subscription tier
```
#### password_reset
```
{{ customer_name }} - Customer's name
{{ reset_link }} - Password reset URL
{{ expiry_hours }} - Link expiration time
```
### Order Templates
#### order_confirmation
```
{{ customer_name }} - Customer's name
{{ order_number }} - Order reference
{{ order_total }} - Order total amount
{{ order_items_count }} - Number of items
{{ order_date }} - Order date
{{ shipping_address }} - Delivery address
```
### Common Variables (All Templates)
```
{{ platform_name }} - "Orion" or whitelabel name
{{ platform_logo_url }} - Platform logo URL
{{ support_email }} - Support email address
{{ store_name }} - Store's business name
{{ store_logo_url }} - Store's logo URL
```
---
## Troubleshooting
### Email Not Received
1. Check spam/junk folder
2. Verify email address is correct
3. Check email logs in admin dashboard
4. Verify SMTP configuration
### Template Not Applying
1. Clear browser cache
2. Verify the correct language is selected
3. Check if store override exists
4. Verify template is not platform-only
### Variables Not Replaced
1. Check variable spelling (case-sensitive)
2. Ensure variable is available for this template
3. Wrap variables in `{{ }}` syntax
4. Check for typos in variable names
---
## API Reference
For developers integrating with the email system:
### Sending a Template Email
```python
from app.services.email_service import EmailService
email_service = EmailService(db)
email_service.send_template(
template_code="order_confirmation",
to_email="customer@example.com",
to_name="John Doe",
language="fr",
variables={
"customer_name": "John",
"order_number": "ORD-12345",
"order_total": "99.99",
},
store_id=store.id,
related_type="order",
related_id=order.id,
)
```
See [Email Templates Architecture](../implementation/email-templates-architecture.md) for full technical documentation.
This document has moved to the CMS module docs: [Email Templates Guide](../modules/cms/email-templates-guide.md)

View File

@@ -1,366 +1 @@
# Inventory Management
## Overview
The Orion platform provides comprehensive inventory management with support for:
- **Multi-location tracking** - Track stock across warehouses, stores, and storage bins
- **Reservation system** - Reserve items for pending orders
- **Digital products** - Automatic unlimited inventory for digital goods
- **Admin operations** - Manage inventory on behalf of stores
---
## Key Concepts
### Storage Locations
Inventory is tracked at the **storage location level**. Each product can have stock in multiple locations:
```
Product: "Wireless Headphones"
├── WAREHOUSE_MAIN: 100 units (10 reserved)
├── WAREHOUSE_WEST: 50 units (0 reserved)
└── STORE_FRONT: 25 units (5 reserved)
Total: 175 units | Reserved: 15 | Available: 160
```
**Location naming:** Locations are text strings, normalized to UPPERCASE (e.g., `WAREHOUSE_A`, `STORE_01`).
### Inventory States
| Field | Description |
|-------|-------------|
| `quantity` | Total physical stock at location |
| `reserved_quantity` | Items reserved for pending orders |
| `available_quantity` | `quantity - reserved_quantity` (can be sold) |
### Product Types & Inventory
| Product Type | Inventory Behavior |
|--------------|-------------------|
| **Physical** | Requires inventory tracking, orders check available stock |
| **Digital** | **Unlimited inventory** - no stock constraints |
| **Service** | Treated as digital (unlimited) |
| **Subscription** | Treated as digital (unlimited) |
---
## Digital Products
Digital products have **unlimited inventory** by default. This means:
- Orders for digital products never fail due to "insufficient inventory"
- No need to create inventory entries for digital products
- The `available_inventory` property returns `999999` (effectively unlimited)
### How It Works
```python
# In Product model
@property
def has_unlimited_inventory(self) -> bool:
"""Digital products have unlimited inventory."""
return self.is_digital
@property
def available_inventory(self) -> int:
"""Calculate available inventory."""
if self.has_unlimited_inventory:
return 999999 # Unlimited
return sum(inv.available_quantity for inv in self.inventory_entries)
```
### Setting a Product as Digital
Digital products are identified by the `is_digital` flag on the `MarketplaceProduct`:
```python
marketplace_product.is_digital = True
marketplace_product.product_type_enum = "digital"
marketplace_product.digital_delivery_method = "license_key" # or "download", "email"
```
---
## Inventory Operations
### Set Inventory
Replace the exact quantity at a location:
```http
POST /api/v1/store/inventory/set
{
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": 100
}
```
### Adjust Inventory
Add or remove stock (positive = add, negative = remove):
```http
POST /api/v1/store/inventory/adjust
{
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": -10 // Remove 10 units
}
```
### Reserve Inventory
Mark items as reserved for an order:
```http
POST /api/v1/store/inventory/reserve
{
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": 5
}
```
### Release Reservation
Cancel a reservation (order cancelled):
```http
POST /api/v1/store/inventory/release
{
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": 5
}
```
### Fulfill Reservation
Complete an order (items shipped):
```http
POST /api/v1/store/inventory/fulfill
{
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": 5
}
```
This decreases both `quantity` and `reserved_quantity`.
---
## Reservation Workflow
```
┌─────────────────┐
│ Order Created │
└────────┬────────┘
┌─────────────────┐
│ Reserve Items │ reserved_quantity += order_qty
└────────┬────────┘
┌────┴────┐
│ │
▼ ▼
┌───────┐ ┌──────────┐
│Cancel │ │ Ship │
└───┬───┘ └────┬─────┘
│ │
▼ ▼
┌─────────┐ ┌──────────────┐
│ Release │ │ Fulfill │
│reserved │ │ quantity -= │
│ -= qty │ │ reserved -= │
└─────────┘ └──────────────┘
```
---
## Admin Inventory Management
Administrators can manage inventory on behalf of any store through the admin UI at `/admin/inventory` or via the API.
### Admin UI Features
The admin inventory page provides:
- **Overview Statistics** - Total entries, stock quantities, reserved items, and low stock alerts
- **Filtering** - Filter by store, location, and low stock threshold
- **Search** - Search by product title or SKU
- **Stock Adjustment** - Add or remove stock with optional reason tracking
- **Set Quantity** - Set exact stock quantity at any location
- **Delete Entries** - Remove inventory entries
### Admin API Endpoints
### List All Inventory
```http
GET /api/v1/admin/inventory
GET /api/v1/admin/inventory?store_id=1
GET /api/v1/admin/inventory?low_stock=10
```
### Get Inventory Statistics
```http
GET /api/v1/admin/inventory/stats
Response:
{
"total_entries": 150,
"total_quantity": 5000,
"total_reserved": 200,
"total_available": 4800,
"low_stock_count": 12,
"stores_with_inventory": 5,
"unique_locations": 8
}
```
### Low Stock Alerts
```http
GET /api/v1/admin/inventory/low-stock?threshold=10
Response:
[
{
"product_id": 123,
"store_name": "TechStore",
"product_title": "USB Cable",
"location": "WAREHOUSE_A",
"quantity": 3,
"available_quantity": 2
}
]
```
### Set Inventory (Admin)
```http
POST /api/v1/admin/inventory/set
{
"store_id": 1,
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": 100
}
```
### Adjust Inventory (Admin)
```http
POST /api/v1/admin/inventory/adjust
{
"store_id": 1,
"product_id": 123,
"location": "WAREHOUSE_A",
"quantity": 25,
"reason": "Restocking from supplier"
}
```
---
## Database Schema
### Inventory Table
```sql
CREATE TABLE inventory (
id SERIAL PRIMARY KEY,
product_id INTEGER NOT NULL REFERENCES products(id),
store_id INTEGER NOT NULL REFERENCES stores(id),
location VARCHAR NOT NULL,
quantity INTEGER NOT NULL DEFAULT 0,
reserved_quantity INTEGER DEFAULT 0,
gtin VARCHAR,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(product_id, location)
);
CREATE INDEX idx_inventory_store_product ON inventory(store_id, product_id);
CREATE INDEX idx_inventory_product_location ON inventory(product_id, location);
```
### Constraints
- **Unique constraint:** `(product_id, location)` - One entry per product/location
- **Foreign keys:** References `products` and `stores` tables
- **Non-negative:** `quantity` and `reserved_quantity` must be >= 0
---
## Best Practices
### Physical Products
1. **Create inventory entries** before accepting orders
2. **Use meaningful location names** (e.g., `WAREHOUSE_MAIN`, `STORE_NYC`)
3. **Monitor low stock** using the admin dashboard or API
4. **Reserve on order creation** to prevent overselling
### Digital Products
1. **No inventory setup needed** - unlimited by default
2. **Optional:** Create entries for license key tracking
3. **Focus on fulfillment** - digital delivery mechanism
### Multi-Location
1. **Aggregate queries** use `Product.total_inventory` and `Product.available_inventory`
2. **Location-specific** operations use the Inventory model directly
3. **Transfers** between locations: adjust down at source, adjust up at destination
---
## API Reference
### Store Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/store/inventory/set` | Set exact quantity |
| POST | `/api/v1/store/inventory/adjust` | Add/remove quantity |
| POST | `/api/v1/store/inventory/reserve` | Reserve for order |
| POST | `/api/v1/store/inventory/release` | Cancel reservation |
| POST | `/api/v1/store/inventory/fulfill` | Complete order |
| GET | `/api/v1/store/inventory/product/{id}` | Product summary |
| GET | `/api/v1/store/inventory` | List with filters |
| PUT | `/api/v1/store/inventory/{id}` | Update entry |
| DELETE | `/api/v1/store/inventory/{id}` | Delete entry |
### Admin Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/admin/inventory` | List all (cross-store) |
| GET | `/api/v1/admin/inventory/stats` | Platform statistics |
| GET | `/api/v1/admin/inventory/low-stock` | Low stock alerts |
| GET | `/api/v1/admin/inventory/stores` | Stores with inventory |
| GET | `/api/v1/admin/inventory/locations` | Unique locations |
| GET | `/api/v1/admin/inventory/stores/{id}` | Store inventory |
| GET | `/api/v1/admin/inventory/products/{id}` | Product summary |
| POST | `/api/v1/admin/inventory/set` | Set (requires store_id) |
| POST | `/api/v1/admin/inventory/adjust` | Adjust (requires store_id) |
| PUT | `/api/v1/admin/inventory/{id}` | Update entry |
| DELETE | `/api/v1/admin/inventory/{id}` | Delete entry |
---
## Related Documentation
- [Product Management](product-management.md)
- [Admin Inventory Migration Plan](../implementation/inventory-admin-migration.md)
- [Store Operations Expansion](../development/migration/store-operations-expansion.md)
This document has moved to the inventory module docs: [User Guide](../modules/inventory/user-guide.md)

View File

@@ -1,261 +1,3 @@
# Letzshop Admin Management Guide
Complete guide for managing Letzshop integration from the Admin Portal at `/admin/marketplace/letzshop`.
## Table of Contents
- [Overview](#overview)
- [Store Selection](#store-selection)
- [Products Tab](#products-tab)
- [Orders Tab](#orders-tab)
- [Exceptions Tab](#exceptions-tab)
- [Jobs Tab](#jobs-tab)
- [Settings Tab](#settings-tab)
---
## Overview
The Letzshop Management page provides a unified interface for managing Letzshop marketplace integration for all stores. Key features:
- **Multi-Store Support**: Select any store to manage their Letzshop integration
- **Product Management**: View, import, and export products
- **Order Processing**: View orders, confirm inventory, set tracking
- **Exception Handling**: Resolve product matching exceptions
- **Job Monitoring**: Track import, export, and sync operations
- **Configuration**: Manage CSV URLs, credentials, and sync settings
---
## Store Selection
At the top of the page, use the store autocomplete to select which store to manage:
1. Type to search for a store by name or code
2. Select from the dropdown
3. The page loads store-specific data for all tabs
4. Your selection is saved and restored on next visit
**Cross-Store View**: When no store is selected, the Orders and Exceptions tabs show data across all stores.
---
## Products Tab
The Products tab displays Letzshop marketplace products imported for the selected store.
### Product Listing
- **Search**: Filter by title, GTIN, SKU, or brand
- **Status Filter**: Show all, active only, or inactive only
- **Pagination**: Navigate through product pages
### Product Table Columns
| Column | Description |
|--------|-------------|
| Product | Image, title, and brand |
| Identifiers | GTIN and SKU codes |
| Price | Product price with currency |
| Status | Active/Inactive badge |
| Actions | View product details |
### Import Products
Click the **Import** button to open the import modal:
1. **Import Single Language**: Select a language and enter the CSV URL
2. **Import All Languages**: Imports from all configured CSV URLs (FR, DE, EN)
Import settings (batch size) are configured in the Settings tab.
### Export Products
Click the **Export** button to export products to the Letzshop pickup folder:
- Exports all three languages (FR, DE, EN) automatically
- Files are placed in `exports/letzshop/{store_code}/`
- Filename format: `{store_code}_products_{language}.csv`
- The export is logged and appears in the Jobs tab
Export settings (include inactive products) are configured in the Settings tab.
---
## Orders Tab
The Orders tab displays orders from Letzshop for the selected store (or all stores if none selected).
### Order Listing
- **Search**: Filter by order number, customer name, or email
- **Status Filter**: All, Pending, Confirmed, Shipped, Declined
- **Date Range**: Filter by order date
### Order Actions
| Action | Description |
|--------|-------------|
| View | Open order details modal |
| Confirm | Confirm all items in order |
| Decline | Decline all items in order |
| Set Tracking | Add tracking number and carrier |
### Order Details Modal
Shows complete order information including:
- Order number and date
- Customer name and email
- Shipping address
- Order items with confirmation status
- Tracking information (if set)
---
## Exceptions Tab
The Exceptions tab shows product matching exceptions that need resolution. See the [Order Item Exceptions documentation](../implementation/order-item-exceptions.md) for details.
### Exception Types
When an order is imported and a product cannot be matched by GTIN:
1. The order is imported with a placeholder product
2. An exception is created for resolution
3. The order cannot be confirmed until exceptions are resolved
### Resolution Actions
| Action | Description |
|--------|-------------|
| Resolve | Assign the correct product to the order item |
| Bulk Resolve | Resolve all exceptions for the same GTIN |
| Ignore | Mark as ignored (still blocks confirmation) |
---
## Jobs Tab
The Jobs tab provides a unified view of all Letzshop-related operations for the selected store.
### Job Types
| Type | Icon | Color | Description |
|------|------|-------|-------------|
| Product Import | Cloud Download | Purple | Importing products from Letzshop CSV |
| Product Export | Cloud Upload | Blue | Exporting products to pickup folder |
| Historical Import | Clock | Orange | Importing historical orders |
| Order Sync | Refresh | Indigo | Syncing orders from Letzshop API |
### Job Information
Each job displays:
- **ID**: Unique job identifier
- **Type**: Import, Export, Historical Import, or Order Sync
- **Status**: Pending, Processing, Completed, Failed, or Partial
- **Records**: Success count / Total processed (failed count if any)
- **Started**: When the job began
- **Duration**: How long the job took
#### Records Column Meaning
| Job Type | Records Shows |
|----------|---------------|
| Product Import | Products imported / Total products |
| Product Export | Files exported / Total files (3 languages) |
| Historical Import | Orders imported / Total orders |
| Order Sync | Orders synced / Total orders |
### Filtering
- **Type Filter**: Show specific job types
- **Status Filter**: Show jobs with specific status
### Job Actions
| Action | Description |
|--------|-------------|
| View Errors | Show error details (for failed jobs) |
| View Details | Show complete job information |
---
## Settings Tab
The Settings tab manages Letzshop integration configuration for the selected store.
### CSV Feed URLs
Configure the URLs for Letzshop product CSV feeds:
- **French (FR)**: URL for French product data
- **German (DE)**: URL for German product data
- **English (EN)**: URL for English product data
### Import Settings
- **Batch Size**: Number of products to process per batch (100-5000)
### Export Settings
- **Include Inactive**: Whether to include inactive products in exports
### API Credentials
Configure Letzshop API access:
- **API Key**: Your Letzshop API key (encrypted at rest)
- **Test Connection**: Verify API connectivity
### Sync Settings
- **Auto-Sync Enabled**: Enable automatic order synchronization
- **Sync Interval**: How often to sync orders (in minutes)
---
## API Endpoints
### Products
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/admin/products` | GET | List marketplace products with filters |
| `/admin/products/stats` | GET | Get product statistics |
| `/admin/letzshop/stores/{id}/export` | GET | Download CSV export |
| `/admin/letzshop/stores/{id}/export` | POST | Export to pickup folder |
### Jobs
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/admin/letzshop/stores/{id}/jobs` | GET | List jobs for store |
| `/admin/marketplace-import-jobs` | POST | Create import job |
### Orders
See [Letzshop Order Integration](letzshop-order-integration.md) for complete order API documentation.
---
## Best Practices
### Product Management
1. **Regular Imports**: Schedule regular imports to keep product data current
2. **Export Before Sync**: Export products before Letzshop's pickup schedule
3. **Monitor Jobs**: Check the Jobs tab for failed imports/exports
### Order Processing
1. **Check Exceptions First**: Resolve exceptions before confirming orders
2. **Verify Tracking**: Ensure tracking numbers are valid before submission
3. **Monitor Sync Status**: Check for failed order syncs in Jobs tab
### Troubleshooting
1. **Products Not Appearing**: Verify CSV URL is accessible and valid
2. **Export Failed**: Check write permissions on exports directory
3. **Orders Not Syncing**: Verify API credentials and test connection
This document has moved to the marketplace module docs: [Admin Guide](../modules/marketplace/admin-guide.md)

View File

@@ -1,322 +1,3 @@
# Letzshop Marketplace Integration
## Introduction
This guide covers the Orion platform's integration with the Letzshop marketplace, including:
- **Product Export**: Generate Letzshop-compatible CSV files from your product catalog
- **Order Import**: Fetch and manage orders from Letzshop
- **Fulfillment Sync**: Confirm/reject orders, set tracking numbers
- **GraphQL API Reference**: Direct API access for advanced use cases
---
## Product Export
### Overview
The Orion platform can export your products to Letzshop-compatible CSV format (Google Shopping feed format). This allows you to:
- Upload your product catalog to Letzshop marketplace
- Generate feeds in multiple languages (English, French, German)
- Include all required Letzshop fields automatically
### API Endpoints
#### Store Export
```http
GET /api/v1/store/letzshop/export?language=fr
Authorization: Bearer <store_token>
```
**Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `language` | string | `en` | Language for title/description (`en`, `fr`, `de`) |
| `include_inactive` | bool | `false` | Include inactive products |
**Response:** CSV file download (`store_code_letzshop_export.csv`)
#### Admin Export
```http
GET /api/v1/admin/letzshop/export?store_id=1&language=fr
Authorization: Bearer <admin_token>
```
**Additional Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `store_id` | int | Required. Store ID to export |
### CSV Format
The export generates a tab-separated CSV file with these columns:
| Column | Description | Example |
|--------|-------------|---------|
| `id` | Product SKU | `PROD-001` |
| `title` | Product title (localized) | `Wireless Headphones` |
| `description` | Product description (localized) | `High-quality...` |
| `link` | Product URL | `https://shop.example.com/product/123` |
| `image_link` | Main product image | `https://cdn.example.com/img.jpg` |
| `additional_image_link` | Additional images (comma-separated) | `img2.jpg,img3.jpg` |
| `availability` | Stock status | `in stock` / `out of stock` |
| `price` | Regular price with currency | `49.99 EUR` |
| `sale_price` | Sale price with currency | `39.99 EUR` |
| `brand` | Brand name | `TechBrand` |
| `gtin` | Global Trade Item Number | `0012345678901` |
| `mpn` | Manufacturer Part Number | `TB-WH-001` |
| `google_product_category` | Google category ID | `Electronics > Audio` |
| `condition` | Product condition | `new` / `used` / `refurbished` |
| `atalanda:tax_rate` | Luxembourg VAT rate | `17` |
### Multi-Language Support
Products are exported with localized content based on the `language` parameter:
```bash
# French export
curl -H "Authorization: Bearer $TOKEN" \
"https://api.example.com/api/v1/store/letzshop/export?language=fr"
# German export
curl -H "Authorization: Bearer $TOKEN" \
"https://api.example.com/api/v1/store/letzshop/export?language=de"
# English export (default)
curl -H "Authorization: Bearer $TOKEN" \
"https://api.example.com/api/v1/store/letzshop/export?language=en"
```
If a translation is not available for the requested language, the system falls back to English, then to any available translation.
### Using the Export
1. **Navigate to Letzshop** in your store dashboard
2. **Click the Export tab**
3. **Select your language** (French, German, or English)
4. **Click "Download CSV"**
5. **Upload to Letzshop** via their merchant portal
---
## Order Integration
For details on order import and fulfillment, see [Letzshop Order Integration](./letzshop-order-integration.md).
---
## Letzshop GraphQL API Reference
The following sections document the Letzshop GraphQL API for direct integration.
---
## GraphQL API
Utilizing this API, you can retrieve and modify data on products, stores, and shipments. Letzshop uses GraphQL, allowing for precise queries.
**Endpoint**:
http://letzshop.lu/graphql
Replace YOUR_API_ACCESS_KEY with your actual key or remove the Authorization header for public data.
## Authentication
Some data is public (e.g., store description and product prices).
For protected operations (e.g., orders or vouchers), an API key is required.
Request one via your account manager or email: support@letzshop.lu
Include the key:
Authorization: Bearer YOUR_API_ACCESS_KEY
---
## Playground
- Access the interactive GraphQL Playground via the Letzshop website.
- It provides live docs, auto-complete (CTRL + Space), and run-time testing.
- **Caution**: You're working on production—mutations like confirming orders are real. [1](https://letzshop.lu/en/dev)
---
## Deprecations
The following GraphQL fields will be removed soon:
| Type | Field | Replacement |
|---------|---------------------|----------------------------------|
| Event | latitude, longitude | `#lat`, `#lng` |
| Greco | weight | `packages.weight` |
| Product | ageRestriction | `_age_restriction` (int) |
| Taxon | identifier | `slug` |
| User | billAddress, shipAddress | on `orders` instead |
| Store | facebookLink, instagramLink, twitterLink, youtubeLink | `social_media_links` |
| Store | owner | `representative` |
| Store | permalink | `slug` | [1](https://letzshop.lu/en/dev)
---
## Order Management via API
Using the API, you can:
- Fetch unconfirmed orders
- Confirm or reject them
- Set tracking numbers
- Handle returns
All of this requires at least "shop manager" API key access. Multi-store management is supported if rights allow. [1](https://letzshop.lu/en/dev)
### 1. Retrieve Unconfirmed Shipments
**Query:**
```graphql
query {
shipments(state: unconfirmed) {
nodes {
id
inventoryUnits {
id
state
}
}
}
}
``` [1](https://letzshop.lu/en/dev)
---
### 2. Confirm/Reject Inventory Units
Use inventoryUnit IDs to confirm or reject:
```graphql
mutation {
confirmInventoryUnits(input: {
inventoryUnits: [
{ inventoryUnitId: "ID1", isAvailable: true },
{ inventoryUnitId: "ID2", isAvailable: false },
{ inventoryUnitId: "ID3", isAvailable: false }
]
}) {
inventoryUnits {
id
state
}
errors {
id
code
message
}
}
}
``` [1](https://letzshop.lu/en/dev)
---
### 3. Handle Customer Returns
Use only after receiving returned items:
```graphql
mutation {
returnInventoryUnits(input: {
inventoryUnits: [
{ inventoryUnitId: "ID1" },
{ inventoryUnitId: "ID2" }
]
}) {
inventoryUnits {
id
state
}
errors {
id
code
}
}
}
``` [1](https://letzshop.lu/en/dev)
---
### 4. Set Shipment Tracking Number
Include shipping provider and tracking code:
```graphql
mutation {
setShipmentTracking(input: {
shipmentId: "SHIPMENT_ID",
code: "TRACK123",
provider: THE_SHIPPING_PROVIDER
}) {
shipment {
tracking {
code
provider
}
}
errors {
code
message
}
}
}
``` [1](https://letzshop.lu/en/dev)
---
## Event System
Subscribe by contacting support@letzshop.lu. Events are delivered via an SNS-like message structure:
```json
{
"Type": "Notification",
"MessageId": "XXX",
"TopicArn": "arn:aws:sns:eu-central-1:XXX:events",
"Message": "{\"id\":\"XXX\",\"type\":\"XXX\",\"payload\":{...}}",
"Timestamp": "2019-01-01T00:00:00.000Z",
"SignatureVersion": "1",
"Signature": "XXX",
"SigningCertURL": "...",
"UnsubscribeURL": "..."
}
``` [1](https://letzshop.lu/en/dev)
### Message Payload
Each event includes:
- `id`
- `type` (e.g., ShipmentConfirmed, UserCreated…)
- `payload` (object-specific data) [1](https://letzshop.lu/en/dev)
---
## Event Types & Payload Structure
A variety of event types are supported. Common ones include:
- `ShipmentConfirmed`, `ShipmentRefundCreated`
- `UserAnonymized`, `UserCreated`, `UserDestroyed`, `UserUpdated`
- `VariantWithPriceCrossedCreated`, `...Updated`
- `StoreCategoryCreated`, `Destroyed`, `Updated`
- `StoreCreated`, `Destroyed`, `Updated`
Exact payload structure varies per event type. [1](https://letzshop.lu/en/dev)
---
## Conclusion
This Markdown file captures all information from the Letzshop development page, formatted for use in your documentation or GitHub.
This document has moved to the marketplace module docs: [API Reference](../modules/marketplace/api.md)

View File

@@ -1,839 +1,3 @@
# Letzshop Order Integration Guide
Complete guide for bidirectional order management with Letzshop marketplace via GraphQL API.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Setup and Configuration](#setup-and-configuration)
- [Order Import](#order-import)
- [Product Exceptions](#product-exceptions)
- [Fulfillment Operations](#fulfillment-operations)
- [Shipping and Tracking](#shipping-and-tracking)
- [API Reference](#api-reference)
- [Database Models](#database-models)
- [Troubleshooting](#troubleshooting)
---
## Overview
The Letzshop Order Integration provides bidirectional synchronization with Letzshop marketplace:
- **Order Import**: Fetch unconfirmed orders from Letzshop via GraphQL
- **Order Confirmation**: Confirm or reject inventory units
- **Tracking Updates**: Set shipment tracking information
- **Audit Trail**: Complete logging of all sync operations
### Key Features
- **Encrypted Credentials**: API keys stored with Fernet encryption
- **Per-Store Configuration**: Each store manages their own Letzshop connection
- **Admin Oversight**: Platform admins can manage any store's integration
- **Queue-Based Fulfillment**: Retry logic for failed operations
- **Multi-Channel Support**: Orders tracked with channel attribution
---
## Architecture
### System Components
```
┌─────────────────────────────────────────┐
│ Frontend Interfaces │
├─────────────────────────────────────────┤
│ Store Portal Admin Portal │
│ /store/letzshop /admin/letzshop │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ API Layer │
├─────────────────────────────────────────┤
│ /api/v1/store/letzshop/* │
│ /api/v1/admin/letzshop/* │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Service Layer │
├─────────────────────────────────────────┤
│ LetzshopClient CredentialsService│
│ (GraphQL) (Encryption) │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Data Layer │
├─────────────────────────────────────────┤
│ StoreLetzshopCredentials │
│ LetzshopOrder │
│ LetzshopFulfillmentQueue │
│ LetzshopSyncLog │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Letzshop GraphQL API │
│ https://letzshop.lu/graphql │
└─────────────────────────────────────────┘
```
### Data Flow
1. **Credentials Setup**: Store/Admin stores encrypted API key
2. **Order Import**: System fetches unconfirmed shipments from Letzshop
3. **Order Processing**: Orders stored locally with Letzshop IDs
4. **Fulfillment**: Store confirms/rejects orders, sets tracking
5. **Sync Back**: Operations sent to Letzshop via GraphQL mutations
---
## Setup and Configuration
### Prerequisites
- Letzshop API key (obtained from Letzshop merchant portal)
- Active store account on the platform
### Step 1: Configure API Credentials
#### Via Store Portal
1. Navigate to **Settings > Letzshop Integration**
2. Enter your Letzshop API key
3. Click **Test Connection** to verify
4. Enable **Auto-Sync** if desired (optional)
5. Click **Save**
#### Via Admin Portal
1. Navigate to **Marketplace > Letzshop**
2. Select the store from the list
3. Click **Configure Credentials**
4. Enter the API key
5. Click **Save & Test**
### Step 2: Test Connection
```bash
# Test connection via API
curl -X POST /api/v1/store/letzshop/test \
-H "Authorization: Bearer $TOKEN"
```
Response:
```json
{
"success": true,
"message": "Connection successful",
"response_time_ms": 245.5
}
```
### Configuration Options
| Setting | Default | Description |
|---------|---------|-------------|
| `api_endpoint` | `https://letzshop.lu/graphql` | GraphQL endpoint URL |
| `auto_sync_enabled` | `false` | Enable automatic order sync |
| `sync_interval_minutes` | `15` | Auto-sync interval (5-1440 minutes) |
---
## Order Import
### Manual Import
Import orders on-demand via the store portal or API:
```bash
# Trigger order import
curl -X POST /api/v1/store/letzshop/orders/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"operation": "order_import"}'
```
Response:
```json
{
"success": true,
"message": "Import completed: 5 imported, 2 updated",
"orders_imported": 5,
"orders_updated": 2,
"errors": []
}
```
### What Gets Imported
The import fetches **unconfirmed shipments** from Letzshop containing:
- Order ID and number
- Customer email and name
- Order total and currency
- Inventory units (products to fulfill)
- Shipping/billing addresses
- Current order state
### Order States
| Letzshop State | Description |
|----------------|-------------|
| `unconfirmed` | Awaiting store confirmation |
| `confirmed` | Store confirmed, ready to ship |
| `shipped` | Tracking number set |
| `delivered` | Delivery confirmed |
| `returned` | Items returned |
### Sync Status
Local orders track their sync status:
| Status | Description |
|--------|-------------|
| `pending` | Imported, awaiting action |
| `confirmed` | Confirmed with Letzshop |
| `rejected` | Rejected with Letzshop |
| `shipped` | Tracking set with Letzshop |
---
## Product Exceptions
When importing orders from Letzshop, products are matched by GTIN. If a product is not found in the store's catalog, the system **gracefully imports the order** with a placeholder product and creates an exception record for resolution.
### Exception Workflow
```
Import Order → Product not found by GTIN
Create order with placeholder
+ Flag item: needs_product_match=True
+ Create OrderItemException record
Exception appears in QC dashboard
┌───────────┴───────────┐
│ │
Resolve Ignore
(assign product) (with reason)
│ │
▼ ▼
Order can be confirmed Still blocks confirmation
```
### Exception Types
| Type | Description |
|------|-------------|
| `product_not_found` | GTIN not in store's product catalog |
| `gtin_mismatch` | GTIN format issue |
| `duplicate_gtin` | Multiple products with same GTIN |
### Exception Statuses
| Status | Description | Blocks Confirmation |
|--------|-------------|---------------------|
| `pending` | Awaiting resolution | **Yes** |
| `resolved` | Product assigned | No |
| `ignored` | Marked as ignored | **Yes** |
**Important:** Both `pending` and `ignored` exceptions block order confirmation to Letzshop.
### Viewing Exceptions
Navigate to **Marketplace > Letzshop > Exceptions** tab to see all unmatched products.
The dashboard shows:
- **Pending**: Exceptions awaiting resolution
- **Resolved**: Exceptions that have been matched
- **Ignored**: Exceptions marked as ignored
- **Orders Affected**: Orders with at least one exception
### Resolving Exceptions
#### Via Admin UI
1. Navigate to **Marketplace > Letzshop > Exceptions**
2. Click **Resolve** on the pending exception
3. Search for the correct product by name, SKU, or GTIN
4. Select the product and click **Confirm**
5. Optionally check "Apply to all exceptions with this GTIN" for bulk resolution
#### Via API
```bash
# Resolve a single exception
curl -X POST /api/v1/admin/order-exceptions/{exception_id}/resolve \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"product_id": 123,
"notes": "Matched to correct product manually"
}'
# Bulk resolve all exceptions with same GTIN
curl -X POST /api/v1/admin/order-exceptions/bulk-resolve?store_id=1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"gtin": "4006381333931",
"product_id": 123,
"notes": "Product imported to catalog"
}'
```
### Auto-Matching
When products are imported to the store catalog (via product sync or manual import), the system automatically:
1. Collects GTINs of newly imported products
2. Finds pending exceptions with matching GTINs
3. Resolves them by assigning the new product
This happens during:
- Single product import (`copy_to_store_catalog`)
- Bulk marketplace sync
### Exception Statistics
Get counts via API:
```bash
curl -X GET /api/v1/admin/order-exceptions/stats?store_id=1 \
-H "Authorization: Bearer $TOKEN"
```
Response:
```json
{
"pending": 15,
"resolved": 42,
"ignored": 3,
"total": 60,
"orders_with_exceptions": 8
}
```
For more details, see [Order Item Exception System](../implementation/order-item-exceptions.md).
---
## Fulfillment Operations
### Confirm Order
Confirm that you can fulfill the order:
```bash
# Confirm all inventory units in an order
curl -X POST /api/v1/store/letzshop/orders/{order_id}/confirm \
-H "Authorization: Bearer $TOKEN"
# Or confirm specific units
curl -X POST /api/v1/store/letzshop/orders/{order_id}/confirm \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"inventory_unit_ids": ["unit_abc123", "unit_def456"]}'
```
### Reject Order
Reject order if you cannot fulfill:
```bash
curl -X POST /api/v1/store/letzshop/orders/{order_id}/reject \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "Out of stock"}'
```
### Set Tracking
Add tracking information for shipment:
```bash
curl -X POST /api/v1/store/letzshop/orders/{order_id}/tracking \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tracking_number": "1Z999AA10123456784",
"tracking_carrier": "ups"
}'
```
Supported carriers: `dhl`, `ups`, `fedex`, `post_lu`, etc.
---
## Shipping and Tracking
The system captures shipping information from Letzshop and provides local shipping management features.
### Letzshop Nomenclature
Letzshop uses specific terminology for order references:
| Term | Example | Description |
|------|---------|-------------|
| **Order Number** | `R532332163` | Customer-facing order reference |
| **Shipment Number** | `H74683403433` | Carrier shipment ID for tracking |
| **Hash ID** | `nvDv5RQEmCwbjo` | Internal Letzshop reference |
### Order Fields
Orders imported from Letzshop include:
| Field | Description |
|-------|-------------|
| `external_order_number` | Letzshop order number (e.g., R532332163) |
| `shipment_number` | Carrier shipment number (e.g., H74683403433) |
| `shipping_carrier` | Carrier code (greco, colissimo, xpresslogistics) |
| `tracking_number` | Tracking number (if available) |
| `tracking_url` | Full tracking URL |
### Carrier Detection
The system automatically detects the carrier from Letzshop shipment data:
| Carrier | Code | Label URL Prefix |
|---------|------|------------------|
| Greco | `greco` | `https://dispatchweb.fr/Tracky/Home/` |
| Colissimo | `colissimo` | Configurable in settings |
| XpressLogistics | `xpresslogistics` | Configurable in settings |
### Mark as Shipped
Mark orders as shipped locally (does **not** sync to Letzshop):
```bash
curl -X POST /api/v1/admin/orders/{order_id}/ship \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tracking_number": "1Z999AA10123456784",
"tracking_url": "https://tracking.example.com/1Z999AA10123456784",
"shipping_carrier": "ups"
}'
```
**Note:** This updates the local order status to `shipped` and sets the `shipped_at` timestamp. It does not send anything to Letzshop API.
### Download Shipping Label
Get the shipping label URL for an order:
```bash
curl -X GET /api/v1/admin/orders/{order_id}/shipping-label \
-H "Authorization: Bearer $TOKEN"
```
Response:
```json
{
"shipment_number": "H74683403433",
"shipping_carrier": "greco",
"label_url": "https://dispatchweb.fr/Tracky/Home/H74683403433",
"tracking_number": null,
"tracking_url": null
}
```
The label URL is constructed from:
- **Carrier label URL prefix** (configured in Admin Settings)
- **Shipment number** from the order
### Carrier Label Settings
Configure carrier label URL prefixes in **Admin > Settings > Shipping**:
| Setting | Default | Description |
|---------|---------|-------------|
| Greco Label URL | `https://dispatchweb.fr/Tracky/Home/` | Greco tracking/label prefix |
| Colissimo Label URL | *(empty)* | Colissimo tracking prefix |
| XpressLogistics Label URL | *(empty)* | XpressLogistics prefix |
The full label URL is: `{prefix}{shipment_number}`
### Tracking Information
Letzshop does not expose Greco tracking information via API. The tracking URL visible in the Letzshop web UI is auto-generated by Letzshop using the dispatchweb.fr prefix.
For orders using Greco carrier:
1. The shipment number (e.g., `H74683403433`) is captured during import
2. The tracking URL can be constructed: `https://dispatchweb.fr/Tracky/Home/{shipment_number}`
3. Use the Download Label feature to get this URL
---
## API Reference
### Store Endpoints
Base path: `/api/v1/store/letzshop`
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/status` | Get integration status |
| GET | `/credentials` | Get credentials (API key masked) |
| POST | `/credentials` | Create/update credentials |
| PATCH | `/credentials` | Partial update credentials |
| DELETE | `/credentials` | Remove credentials |
| POST | `/test` | Test stored credentials |
| POST | `/test-key` | Test API key without saving |
| GET | `/orders` | List Letzshop orders |
| GET | `/orders/{id}` | Get order details |
| POST | `/orders/import` | Import orders from Letzshop |
| POST | `/orders/{id}/confirm` | Confirm order |
| POST | `/orders/{id}/reject` | Reject order |
| POST | `/orders/{id}/tracking` | Set tracking info |
| GET | `/logs` | List sync logs |
| GET | `/queue` | List fulfillment queue |
### Admin Endpoints
Base path: `/api/v1/admin/letzshop`
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/stores` | List stores with Letzshop status |
| GET | `/stores/{id}/credentials` | Get store credentials |
| POST | `/stores/{id}/credentials` | Set store credentials |
| PATCH | `/stores/{id}/credentials` | Update store credentials |
| DELETE | `/stores/{id}/credentials` | Delete store credentials |
| POST | `/stores/{id}/test` | Test store connection |
| POST | `/test` | Test any API key |
| GET | `/stores/{id}/orders` | List store's Letzshop orders |
| POST | `/stores/{id}/sync` | Trigger sync for store |
### Order Endpoints
Base path: `/api/v1/admin/orders`
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `` | List orders (cross-store) |
| GET | `/stats` | Get order statistics |
| GET | `/stores` | Get stores with orders |
| GET | `/{id}` | Get order details |
| PATCH | `/{id}/status` | Update order status |
| POST | `/{id}/ship` | Mark as shipped |
| GET | `/{id}/shipping-label` | Get shipping label URL |
### Exception Endpoints
Base path: `/api/v1/admin/order-exceptions`
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `` | List exceptions |
| GET | `/stats` | Get exception statistics |
| GET | `/{id}` | Get exception details |
| POST | `/{id}/resolve` | Resolve with product |
| POST | `/{id}/ignore` | Mark as ignored |
| POST | `/bulk-resolve` | Bulk resolve by GTIN |
### Response Schemas
#### Credentials Response
```json
{
"id": 1,
"store_id": 5,
"api_key_masked": "letz****",
"api_endpoint": "https://letzshop.lu/graphql",
"auto_sync_enabled": false,
"sync_interval_minutes": 15,
"last_sync_at": "2025-01-15T10:30:00Z",
"last_sync_status": "success",
"last_sync_error": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
```
#### Order Response
```json
{
"id": 123,
"store_id": 5,
"letzshop_order_id": "gid://letzshop/Order/12345",
"letzshop_shipment_id": "gid://letzshop/Shipment/67890",
"letzshop_order_number": "LS-2025-001234",
"letzshop_state": "unconfirmed",
"customer_email": "customer@example.com",
"customer_name": "John Doe",
"total_amount": "99.99",
"currency": "EUR",
"sync_status": "pending",
"inventory_units": [
{"id": "gid://letzshop/InventoryUnit/111", "state": "unconfirmed"}
],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}
```
---
## Database Models
### StoreLetzshopCredentials
Stores encrypted API credentials per store.
```python
class StoreLetzshopCredentials(Base):
__tablename__ = "store_letzshop_credentials"
id: int # Primary key
store_id: int # FK to stores (unique)
api_key_encrypted: str # Fernet encrypted API key
api_endpoint: str # GraphQL endpoint URL
auto_sync_enabled: bool # Enable auto-sync
sync_interval_minutes: int # Sync interval
last_sync_at: datetime # Last sync timestamp
last_sync_status: str # success, failed, partial
last_sync_error: str # Error message if failed
```
### LetzshopOrder
Tracks imported orders from Letzshop.
```python
class LetzshopOrder(Base):
__tablename__ = "letzshop_orders"
id: int # Primary key
store_id: int # FK to stores
letzshop_order_id: str # Letzshop order GID
letzshop_shipment_id: str # Letzshop shipment GID
letzshop_order_number: str # Human-readable order number
local_order_id: int # FK to orders (if imported locally)
letzshop_state: str # Current Letzshop state
customer_email: str # Customer email
customer_name: str # Customer name
total_amount: str # Order total
currency: str # Currency code
raw_order_data: JSON # Full order data from Letzshop
inventory_units: JSON # List of inventory units
sync_status: str # pending, confirmed, rejected, shipped
tracking_number: str # Tracking number (if set)
tracking_carrier: str # Carrier code
```
### LetzshopFulfillmentQueue
Queue for outbound operations with retry logic.
```python
class LetzshopFulfillmentQueue(Base):
__tablename__ = "letzshop_fulfillment_queue"
id: int # Primary key
store_id: int # FK to stores
letzshop_order_id: int # FK to letzshop_orders
operation: str # confirm, reject, set_tracking
payload: JSON # Operation data
status: str # pending, processing, completed, failed
attempts: int # Retry count
max_attempts: int # Max retries (default 3)
error_message: str # Last error if failed
response_data: JSON # Response from Letzshop
```
### LetzshopSyncLog
Audit trail for all sync operations.
```python
class LetzshopSyncLog(Base):
__tablename__ = "letzshop_sync_logs"
id: int # Primary key
store_id: int # FK to stores
operation_type: str # order_import, confirm, etc.
direction: str # inbound, outbound
status: str # success, failed, partial
records_processed: int # Total records
records_succeeded: int # Successful records
records_failed: int # Failed records
error_details: JSON # Detailed error info
started_at: datetime # Operation start time
completed_at: datetime # Operation end time
duration_seconds: int # Total duration
triggered_by: str # user_id, scheduler, webhook
```
---
## Security
### API Key Encryption
API keys are encrypted using Fernet symmetric encryption:
```python
from app.utils.encryption import encrypt_value, decrypt_value
# Encrypt before storing
encrypted_key = encrypt_value(api_key)
# Decrypt when needed
api_key = decrypt_value(encrypted_key)
```
The encryption key is derived from the application's `jwt_secret_key` using PBKDF2.
### Access Control
- **Stores**: Can only manage their own Letzshop integration
- **Admins**: Can manage any store's integration
- **API Keys**: Never returned in plain text (always masked)
---
## Troubleshooting
### Connection Failed
**Symptoms**: "Connection failed" error when testing
**Possible Causes**:
- Invalid API key
- API key expired
- Network issues
- Letzshop service unavailable
**Solutions**:
1. Verify API key in Letzshop merchant portal
2. Regenerate API key if expired
3. Check network connectivity
4. Check Letzshop status page
### Orders Not Importing
**Symptoms**: Import runs but no orders appear
**Possible Causes**:
- No unconfirmed orders in Letzshop
- API key doesn't have required permissions
- Orders already imported
**Solutions**:
1. Check Letzshop dashboard for unconfirmed orders
2. Verify API key has order read permissions
3. Check existing orders with `sync_status: pending`
### Fulfillment Failed
**Symptoms**: Confirm/reject/tracking operations fail
**Possible Causes**:
- Order already processed
- Invalid inventory unit IDs
- API permission issues
**Solutions**:
1. Check order state in Letzshop
2. Verify inventory unit IDs are correct
3. Check fulfillment queue for retry status
4. Review error message in response
### Sync Logs
Check sync logs for detailed operation history:
```bash
curl -X GET /api/v1/store/letzshop/logs \
-H "Authorization: Bearer $TOKEN"
```
### Order Has Unresolved Exceptions
**Symptoms**: "Order has X unresolved exception(s)" error when confirming
**Cause**: Order contains items that couldn't be matched to products during import
**Solutions**:
1. Navigate to **Marketplace > Letzshop > Exceptions** tab
2. Find the pending exceptions for this order
3. Either:
- **Resolve**: Assign the correct product from your catalog
- **Ignore**: Mark as ignored if product will never be matched (still blocks confirmation)
4. Retry the confirmation after resolving all exceptions
### Cannot Find Shipping Label
**Symptoms**: "Download Label" returns empty or no URL
**Possible Causes**:
- Shipment number not captured during import
- Carrier label URL prefix not configured
- Unknown carrier type
**Solutions**:
1. Re-sync the order to capture shipment data
2. Check **Admin > Settings > Shipping** for carrier URL prefixes
3. Verify the order has a valid `shipping_carrier` and `shipment_number`
---
## Best Practices
### For Stores
1. **Test connection** after setting up credentials
2. **Import orders regularly** (or enable auto-sync)
3. **Confirm orders promptly** to avoid delays
4. **Set tracking** as soon as shipment is dispatched
5. **Monitor sync logs** for any failures
### For Admins
1. **Review store status** regularly via admin dashboard
2. **Assist stores** with connection issues
3. **Monitor sync logs** for platform-wide issues
4. **Set up alerts** for failed syncs (optional)
---
## Related Documentation
- [Order Item Exception System](../implementation/order-item-exceptions.md)
- [Marketplace Integration (CSV Import)](marketplace-integration.md)
- [Store RBAC](../backend/store-rbac.md)
- [Admin Integration Guide](../backend/admin-integration-guide.md)
- [Exception Handling](../development/exception-handling.md)
---
## Version History
- **v1.2** (2025-12-20): Shipping & Tracking enhancements
- Added `shipment_number`, `shipping_carrier`, `tracking_url` fields to orders
- Carrier detection from Letzshop shipment data (Greco, Colissimo, XpressLogistics)
- Mark as Shipped feature (local only, does not sync to Letzshop)
- Shipping label URL generation using configurable carrier prefixes
- Admin settings for carrier label URL prefixes
- **v1.1** (2025-12-20): Product Exception System
- Graceful order import when products not found by GTIN
- Placeholder product per store for unmatched items
- Exception tracking with pending/resolved/ignored statuses
- Confirmation blocking until exceptions resolved
- Auto-matching when products are imported
- Exceptions tab in admin Letzshop management page
- Bulk resolution by GTIN
- **v1.0** (2025-12-13): Initial Letzshop order integration
- GraphQL client for order import
- Encrypted credential storage
- Fulfillment operations (confirm, reject, tracking)
- Admin and store API endpoints
- Sync logging and queue management
This document has moved to the marketplace module docs: [Order Integration](../modules/marketplace/order-integration.md)

View File

@@ -1,182 +1 @@
# Media Library
The media library provides centralized management of uploaded files (images, documents) for stores. Each store has their own isolated media storage.
## Overview
- **Storage Location**: `uploads/stores/{store_id}/{folder}/`
- **Supported Types**: Images (JPG, PNG, GIF, WebP), Documents (PDF)
- **Max File Size**: 10MB per file
- **Automatic Thumbnails**: Generated for images (200x200px)
## API Endpoints
### Admin Media Management
Admins can manage media for any store:
```
GET /api/v1/admin/media/stores/{store_id} # List store's media
POST /api/v1/admin/media/stores/{store_id}/upload # Upload file
GET /api/v1/admin/media/stores/{store_id}/{id} # Get media details
DELETE /api/v1/admin/media/stores/{store_id}/{id} # Delete media
```
### Query Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `skip` | int | Pagination offset (default: 0) |
| `limit` | int | Items per page (default: 100, max: 1000) |
| `media_type` | string | Filter by type: `image`, `video`, `document` |
| `folder` | string | Filter by folder: `products`, `general`, etc. |
| `search` | string | Search by filename |
### Upload Response
```json
{
"success": true,
"message": "File uploaded successfully",
"media": {
"id": 1,
"filename": "product-image.jpg",
"file_url": "/uploads/stores/1/products/abc123.jpg",
"url": "/uploads/stores/1/products/abc123.jpg",
"thumbnail_url": "/uploads/stores/1/thumbnails/thumb_abc123.jpg",
"media_type": "image",
"file_size": 245760,
"width": 1200,
"height": 800
}
}
```
## Media Picker Component
A reusable Alpine.js component for selecting images from the media library.
### Usage in Templates
```jinja2
{% from 'shared/macros/modals.html' import media_picker_modal %}
{# Single image selection #}
{{ media_picker_modal(
id='media-picker-main',
show_var='showMediaPicker',
store_id_var='storeId',
title='Select Image'
) }}
{# Multiple image selection #}
{{ media_picker_modal(
id='media-picker-additional',
show_var='showMediaPickerAdditional',
store_id_var='storeId',
multi_select=true,
title='Select Additional Images'
) }}
```
### JavaScript Integration
Include the media picker mixin in your Alpine.js component:
```javascript
function myComponent() {
return {
...data(),
// Include media picker functionality
...mediaPickerMixin(() => this.storeId, false),
storeId: null,
// Override to handle selected image
setMainImage(media) {
this.form.image_url = media.url;
},
// Override for multiple images
addAdditionalImages(mediaList) {
const urls = mediaList.map(m => m.url);
this.form.additional_images.push(...urls);
}
};
}
```
### Media Picker Mixin API
| Property/Method | Description |
|-----------------|-------------|
| `showMediaPicker` | Boolean to show/hide main image picker modal |
| `showMediaPickerAdditional` | Boolean to show/hide additional images picker |
| `mediaPickerState` | Object containing loading, media array, selected items |
| `openMediaPickerMain()` | Open picker for main image |
| `openMediaPickerAdditional()` | Open picker for additional images |
| `loadMediaLibrary()` | Fetch media from API |
| `uploadMediaFile(event)` | Handle file upload |
| `toggleMediaSelection(media)` | Select/deselect a media item |
| `confirmMediaSelection()` | Confirm selection and call callbacks |
| `setMainImage(media)` | Override to handle main image selection |
| `addAdditionalImages(mediaList)` | Override to handle multiple selections |
## File Storage
### Directory Structure
```
uploads/
└── stores/
└── {store_id}/
├── products/ # Product images
├── general/ # General uploads
└── thumbnails/ # Auto-generated thumbnails
```
### URL Paths
Files are served from `/uploads/` path:
- Full image: `/uploads/stores/1/products/image.jpg`
- Thumbnail: `/uploads/stores/1/thumbnails/thumb_image.jpg`
## Database Model
```python
class MediaFile(Base):
id: int
store_id: int
filename: str # Stored filename (UUID-based)
original_filename: str # Original upload name
file_path: str # Relative path from uploads/
thumbnail_path: str # Thumbnail relative path
media_type: str # image, video, document
mime_type: str # image/jpeg, etc.
file_size: int # Bytes
width: int # Image width
height: int # Image height
folder: str # products, general, etc.
```
## Product Images
Products support both a main image and additional images:
```python
class Product(Base):
primary_image_url: str # Main product image
additional_images: list[str] # Array of additional image URLs
```
### In Product Forms
The product create/edit forms include:
1. **Main Image**: Single image with preview and media picker
2. **Additional Images**: Grid of images with add/remove functionality
Both support:
- Browsing the store's media library
- Uploading new images directly
- Entering external URLs manually
This document has moved to the CMS module docs: [Media Library](../modules/cms/media-library.md)

View File

@@ -1,135 +1,3 @@
# Subscription Tier Management
This guide explains how to manage subscription tiers and assign features to them in the admin panel.
## Accessing Tier Management
Navigate to **Admin → Billing & Subscriptions → Subscription Tiers** or go directly to `/admin/subscription-tiers`.
## Dashboard Overview
The tier management page displays:
### Stats Cards
- **Total Tiers**: Number of configured subscription tiers
- **Active Tiers**: Tiers currently available for subscription
- **Public Tiers**: Tiers visible to stores (excludes enterprise/custom)
- **Est. MRR**: Estimated Monthly Recurring Revenue
### Tier Table
Each tier shows:
| Column | Description |
|--------|-------------|
| # | Display order (affects pricing page order) |
| Code | Unique identifier (e.g., `essential`, `professional`) |
| Name | Display name shown to stores |
| Monthly | Monthly price in EUR |
| Annual | Annual price in EUR (or `-` if not set) |
| Orders/Mo | Monthly order limit (or `Unlimited`) |
| Products | Product limit (or `Unlimited`) |
| Team | Team member limit (or `Unlimited`) |
| Features | Number of features assigned |
| Status | Active, Private, or Inactive |
| Actions | Edit Features, Edit, Activate/Deactivate |
## Managing Tiers
### Creating a New Tier
1. Click **Create Tier** button
2. Fill in the tier details:
- **Code**: Unique lowercase identifier (cannot be changed after creation)
- **Name**: Display name for the tier
- **Monthly Price**: Price in cents (e.g., 4900 for €49.00)
- **Annual Price**: Optional annual price in cents
- **Order Limit**: Leave empty for unlimited
- **Product Limit**: Leave empty for unlimited
- **Team Members**: Leave empty for unlimited
- **Display Order**: Controls sort order on pricing pages
- **Active**: Whether tier is available
- **Public**: Whether tier is visible to stores
3. Click **Create**
### Editing a Tier
1. Click the **pencil icon** on the tier row
2. Modify the tier properties
3. Click **Update**
Note: The tier code cannot be changed after creation.
### Activating/Deactivating Tiers
- Click the **check-circle icon** to activate an inactive tier
- Click the **x-circle icon** to deactivate an active tier
Deactivating a tier:
- Does not affect existing subscriptions
- Hides the tier from new subscription selection
- Can be reactivated at any time
## Managing Features
### Assigning Features to a Tier
1. Click the **puzzle-piece icon** on the tier row
2. A slide-over panel opens showing all available features
3. Features are grouped by category:
- Analytics
- Product Management
- Order Management
- Marketing
- Support
- Integration
- Branding
- Team
4. Check/uncheck features to include in the tier
5. Use **Select all** or **Deselect all** per category for bulk actions
6. The footer shows the total number of selected features
7. Click **Save Features** to apply changes
### Feature Categories
| Category | Example Features |
|----------|------------------|
| Analytics | Basic Analytics, Analytics Dashboard, Custom Reports |
| Product Management | Bulk Edit, Variants, Bundles, Inventory Alerts |
| Order Management | Order Automation, Advanced Fulfillment, Multi-Warehouse |
| Marketing | Discount Codes, Abandoned Cart, Email Marketing, Loyalty |
| Support | Email Support, Priority Support, Phone Support, Dedicated Manager |
| Integration | Basic API, Advanced API, Webhooks, Custom Integrations |
| Branding | Theme Customization, Custom Domain, White Label |
| Team | Team Management, Role Permissions, Audit Logs |
## Best Practices
### Tier Pricing Strategy
1. **Essential**: Entry-level with basic features and limits
2. **Professional**: Mid-tier with increased limits and key integrations
3. **Business**: Full-featured for growing businesses
4. **Enterprise**: Custom pricing with unlimited everything
### Feature Assignment Tips
- Start with fewer features in lower tiers
- Ensure each upgrade tier adds meaningful value
- Keep support features as upgrade incentives
- API access typically belongs in Business+ tiers
### Stripe Integration
For each tier, you can optionally configure:
- **Stripe Product ID**: Link to Stripe product
- **Stripe Monthly Price ID**: Link to monthly price
- **Stripe Annual Price ID**: Link to annual price
These are required for automated billing via Stripe Checkout.
## Related Documentation
- [Subscription & Billing System](../features/subscription-billing.md) - Complete billing documentation
- [Feature Gating System](../implementation/feature-gating-system.md) - Technical feature gating details
This document has moved to the billing module docs: [Tier Management](../modules/billing/tier-management.md)