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:
105
app/modules/catalog/docs/data-model.md
Normal file
105
app/modules/catalog/docs/data-model.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Catalog Data Model
|
||||
|
||||
Entity relationships and database schema for the catalog module.
|
||||
|
||||
## Entity Relationship Overview
|
||||
|
||||
```
|
||||
Store 1──* Product 1──* ProductTranslation
|
||||
│
|
||||
├──* ProductMedia *──1 MediaFile
|
||||
│
|
||||
├──? MarketplaceProduct (source)
|
||||
│
|
||||
└──* Inventory (from inventory module)
|
||||
```
|
||||
|
||||
## Models
|
||||
|
||||
### Product
|
||||
|
||||
Store-specific product with independent copy pattern from marketplace imports. All monetary values stored as integer cents.
|
||||
|
||||
| Field | Type | Constraints | Description |
|
||||
|-------|------|-------------|-------------|
|
||||
| `id` | Integer | PK | Primary key |
|
||||
| `store_id` | Integer | FK, not null | Store ownership |
|
||||
| `marketplace_product_id` | Integer | FK, nullable | Optional marketplace source |
|
||||
| `store_sku` | String | indexed | Store's internal SKU |
|
||||
| `gtin` | String(50) | indexed | EAN/UPC barcode |
|
||||
| `gtin_type` | String(20) | nullable | gtin13, gtin14, gtin12, gtin8, isbn13, isbn10 |
|
||||
| `price_cents` | Integer | nullable | Gross price in cents |
|
||||
| `sale_price_cents` | Integer | nullable | Sale price in cents |
|
||||
| `currency` | String(3) | default "EUR" | Currency code |
|
||||
| `brand` | String | nullable | Product brand |
|
||||
| `condition` | String | nullable | Product condition |
|
||||
| `availability` | String | nullable | Availability status |
|
||||
| `primary_image_url` | String | nullable | Main product image URL |
|
||||
| `additional_images` | JSON | nullable | Array of additional image URLs |
|
||||
| `download_url` | String | nullable | Digital product download URL |
|
||||
| `license_type` | String(50) | nullable | Digital product license |
|
||||
| `tax_rate_percent` | Integer | not null, default 17 | VAT rate (LU: 0, 3, 8, 14, 17) |
|
||||
| `supplier` | String(50) | nullable | codeswholesale, internal, etc. |
|
||||
| `supplier_product_id` | String | nullable | Supplier's product reference |
|
||||
| `cost_cents` | Integer | nullable | Cost to acquire in cents |
|
||||
| `margin_percent_x100` | Integer | nullable | Markup × 100 (2550 = 25.5%) |
|
||||
| `is_digital` | Boolean | default False, indexed | Digital vs physical |
|
||||
| `product_type` | String(20) | default "physical" | physical, digital, service, subscription |
|
||||
| `is_featured` | Boolean | default False | Featured flag |
|
||||
| `is_active` | Boolean | default True | Active flag |
|
||||
| `display_order` | Integer | default 0 | Sort order |
|
||||
| `min_quantity` | Integer | default 1 | Min purchase quantity |
|
||||
| `max_quantity` | Integer | nullable | Max purchase quantity |
|
||||
| `fulfillment_email_template` | String | nullable | Template for digital delivery |
|
||||
| `created_at` | DateTime | tz-aware | Record creation time |
|
||||
| `updated_at` | DateTime | tz-aware | Record update time |
|
||||
|
||||
**Unique Constraint**: `(store_id, marketplace_product_id)`
|
||||
**Composite Indexes**: `(store_id, is_active)`, `(store_id, is_featured)`, `(store_id, store_sku)`, `(supplier, supplier_product_id)`
|
||||
|
||||
**Key Properties**: `price`, `sale_price`, `cost` (euro converters), `net_price_cents` (gross minus VAT), `vat_amount_cents`, `profit_cents`, `profit_margin_percent`, `total_inventory`, `available_inventory`
|
||||
|
||||
### ProductTranslation
|
||||
|
||||
Store-specific multilingual content with SEO fields. Independent copy from marketplace translations.
|
||||
|
||||
| Field | Type | Constraints | Description |
|
||||
|-------|------|-------------|-------------|
|
||||
| `id` | Integer | PK | Primary key |
|
||||
| `product_id` | Integer | FK, not null, cascade | Parent product |
|
||||
| `language` | String(5) | not null | en, fr, de, lb |
|
||||
| `title` | String | nullable | Product title |
|
||||
| `description` | Text | nullable | Full description |
|
||||
| `short_description` | String(500) | nullable | Abbreviated description |
|
||||
| `meta_title` | String(70) | nullable | SEO title |
|
||||
| `meta_description` | String(160) | nullable | SEO description |
|
||||
| `url_slug` | String(255) | nullable | URL-friendly slug |
|
||||
| `created_at` | DateTime | tz-aware | Record creation time |
|
||||
| `updated_at` | DateTime | tz-aware | Record update time |
|
||||
|
||||
**Unique Constraint**: `(product_id, language)`
|
||||
|
||||
### ProductMedia
|
||||
|
||||
Association between products and media files with usage tracking.
|
||||
|
||||
| Field | Type | Constraints | Description |
|
||||
|-------|------|-------------|-------------|
|
||||
| `id` | Integer | PK | Primary key |
|
||||
| `product_id` | Integer | FK, not null, cascade | Product reference |
|
||||
| `media_id` | Integer | FK, not null, cascade | Media file reference |
|
||||
| `usage_type` | String(50) | default "gallery" | main_image, gallery, variant, thumbnail, swatch |
|
||||
| `display_order` | Integer | default 0 | Sort order |
|
||||
| `variant_id` | Integer | nullable | Variant reference |
|
||||
| `created_at` | DateTime | tz-aware | Record creation time |
|
||||
| `updated_at` | DateTime | tz-aware | Record update time |
|
||||
|
||||
**Unique Constraint**: `(product_id, media_id, usage_type)`
|
||||
|
||||
## Design Patterns
|
||||
|
||||
- **Independent copy pattern**: Products are copied from marketplace sources, not linked. Store-specific data diverges independently.
|
||||
- **Money as cents**: All prices, costs, margins stored as integer cents
|
||||
- **Luxembourg VAT**: Supports all LU rates (0%, 3%, 8%, 14%, 17%)
|
||||
- **Multi-type products**: Physical, digital, service, subscription with type-specific fields
|
||||
- **SEO per language**: Meta title and description in each translation
|
||||
Reference in New Issue
Block a user