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:
235
app/modules/loyalty/docs/data-model.md
Normal file
235
app/modules/loyalty/docs/data-model.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Loyalty Data Model
|
||||
|
||||
Entity relationships and database schema for the loyalty module.
|
||||
|
||||
## Entity Relationship Diagram
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ Merchant │ (from tenancy module)
|
||||
│ (one program per │
|
||||
│ merchant) │
|
||||
└──────────┬───────────┘
|
||||
│ 1
|
||||
│
|
||||
┌─────┴─────┐
|
||||
│ │
|
||||
▼ 1 ▼ 1
|
||||
┌──────────┐ ┌──────────────────────┐
|
||||
│ Loyalty │ │ MerchantLoyalty │
|
||||
│ Program │ │ Settings │
|
||||
│ │ │ │
|
||||
│ type │ │ staff_pin_policy │
|
||||
│ stamps │ │ allow_self_enrollment│
|
||||
│ points │ │ allow_void │
|
||||
│ branding │ │ allow_cross_location │
|
||||
│ anti- │ │ require_order_ref │
|
||||
│ fraud │ │ log_ip_addresses │
|
||||
└──┬───┬───┘ └──────────────────────┘
|
||||
│ │
|
||||
│ │ 1..*
|
||||
│ ▼
|
||||
│ ┌──────────────┐
|
||||
│ │ StaffPin │
|
||||
│ │ │
|
||||
│ │ name │
|
||||
│ │ pin_hash │ (bcrypt)
|
||||
│ │ store_id │
|
||||
│ │ failed_ │
|
||||
│ │ attempts │
|
||||
│ │ locked_until │
|
||||
│ └──────────────┘
|
||||
│
|
||||
│ 1..*
|
||||
▼
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ LoyaltyCard │ │ Customer │ (from customers module)
|
||||
│ │ *───1 │ │
|
||||
│ card_number │ └──────────────────┘
|
||||
│ qr_code_data │
|
||||
│ stamp_count │ ┌──────────────────┐
|
||||
│ points_balance │ │ Store │ (from tenancy module)
|
||||
│ google_object_id│ *───1 │ (enrolled_at) │
|
||||
│ apple_serial │ └──────────────────┘
|
||||
│ is_active │
|
||||
└──────┬───────────┘
|
||||
│
|
||||
│ 1..*
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ LoyaltyTransaction │ (immutable audit log)
|
||||
│ │
|
||||
│ transaction_type │
|
||||
│ stamps_delta │ (signed: +1 earn, -N redeem)
|
||||
│ points_delta │ (signed: +N earn, -N redeem)
|
||||
│ stamps_balance_after│
|
||||
│ points_balance_after│
|
||||
│ purchase_amount │
|
||||
│ staff_pin_id │──── FK to StaffPin
|
||||
│ store_id │──── FK to Store (location)
|
||||
│ related_txn_id │──── FK to self (for voids)
|
||||
│ ip_address │
|
||||
│ user_agent │
|
||||
└──────────────────────┘
|
||||
|
||||
┌──────────────────────────┐
|
||||
│ AppleDeviceRegistration │
|
||||
│ │
|
||||
│ card_id │──── FK to LoyaltyCard
|
||||
│ device_library_id │
|
||||
│ push_token │
|
||||
│ │
|
||||
│ UNIQUE(device, card) │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
## Models
|
||||
|
||||
### LoyaltyProgram
|
||||
|
||||
Merchant-wide loyalty program configuration. One program per merchant, shared across all stores.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `merchant_id` | FK (unique) | One program per merchant |
|
||||
| `loyalty_type` | Enum | STAMPS, POINTS, or HYBRID |
|
||||
| `stamps_target` | Integer | Stamps needed for reward |
|
||||
| `stamps_reward_description` | String | Reward description text |
|
||||
| `stamps_reward_value_cents` | Integer | Reward monetary value |
|
||||
| `points_per_euro` | Integer | Points earned per euro spent |
|
||||
| `points_rewards` | JSON | Reward catalog (id, name, points_cost) |
|
||||
| `points_expiration_days` | Integer | Days until points expire (nullable) |
|
||||
| `welcome_bonus_points` | Integer | Points given on enrollment |
|
||||
| `minimum_redemption_points` | Integer | Minimum points to redeem |
|
||||
| `minimum_purchase_cents` | Integer | Minimum purchase for earning |
|
||||
| `cooldown_minutes` | Integer | Minutes between stamps (anti-fraud) |
|
||||
| `max_daily_stamps` | Integer | Max stamps per card per day |
|
||||
| `require_staff_pin` | Boolean | Whether PIN is required |
|
||||
| `card_name` | String | Display name on card |
|
||||
| `card_color` | String | Primary brand color (hex) |
|
||||
| `card_secondary_color` | String | Secondary brand color (hex) |
|
||||
| `logo_url` | String | Logo image URL |
|
||||
| `hero_image_url` | String | Hero/banner image URL |
|
||||
| `google_issuer_id` | String | Google Wallet issuer ID |
|
||||
| `google_class_id` | String | Google Wallet class ID |
|
||||
| `apple_pass_type_id` | String | Apple Wallet pass type identifier |
|
||||
| `terms_text` | Text | Terms and conditions |
|
||||
| `privacy_url` | String | Privacy policy URL |
|
||||
| `is_active` | Boolean | Whether program is live |
|
||||
| `activated_at` | DateTime | When program was activated |
|
||||
|
||||
### LoyaltyCard
|
||||
|
||||
Customer loyalty card linking a customer to a merchant's program. One card per customer per merchant.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `merchant_id` | FK | Links to program's merchant |
|
||||
| `customer_id` | FK | Card owner |
|
||||
| `program_id` | FK | Associated program |
|
||||
| `enrolled_at_store_id` | FK | Store where customer enrolled |
|
||||
| `card_number` | String (unique) | Formatted XXXX-XXXX-XXXX |
|
||||
| `qr_code_data` | String (unique) | URL-safe token for QR codes |
|
||||
| `stamp_count` | Integer | Current stamp count |
|
||||
| `total_stamps_earned` | Integer | Lifetime stamps earned |
|
||||
| `stamps_redeemed` | Integer | Total redemptions |
|
||||
| `points_balance` | Integer | Current points balance |
|
||||
| `total_points_earned` | Integer | Lifetime points earned |
|
||||
| `points_redeemed` | Integer | Total points redeemed |
|
||||
| `total_points_voided` | Integer | Total points voided |
|
||||
| `google_object_id` | String | Google Wallet object ID |
|
||||
| `google_object_jwt` | Text | Google Wallet JWT |
|
||||
| `apple_serial_number` | String | Apple Wallet serial number |
|
||||
| `apple_auth_token` | String | Apple Wallet auth token |
|
||||
| `last_stamp_at` | DateTime | Last stamp timestamp |
|
||||
| `last_points_at` | DateTime | Last points timestamp |
|
||||
| `last_redemption_at` | DateTime | Last redemption timestamp |
|
||||
| `last_activity_at` | DateTime | Last activity of any kind |
|
||||
| `is_active` | Boolean | Whether card is active |
|
||||
|
||||
### LoyaltyTransaction
|
||||
|
||||
Immutable audit log of all loyalty operations. Every stamp, point, redemption, and void is recorded.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `merchant_id` | FK | Merchant program owner |
|
||||
| `card_id` | FK | Affected card |
|
||||
| `store_id` | FK | Store where transaction occurred |
|
||||
| `staff_pin_id` | FK (nullable) | Staff who verified |
|
||||
| `related_transaction_id` | FK (nullable) | For void/return linking |
|
||||
| `transaction_type` | Enum | See transaction types below |
|
||||
| `stamps_delta` | Integer | Signed stamp change |
|
||||
| `points_delta` | Integer | Signed points change |
|
||||
| `stamps_balance_after` | Integer | Stamp count after transaction |
|
||||
| `points_balance_after` | Integer | Points balance after transaction |
|
||||
| `purchase_amount_cents` | Integer | Purchase amount for points earning |
|
||||
| `order_reference` | String | External order reference |
|
||||
| `reward_id` | String | Redeemed reward identifier |
|
||||
| `reward_description` | String | Redeemed reward description |
|
||||
| `ip_address` | String | Client IP (audit) |
|
||||
| `user_agent` | String | Client user agent (audit) |
|
||||
| `notes` | Text | Staff/admin notes |
|
||||
| `transaction_at` | DateTime | When transaction occurred |
|
||||
|
||||
**Transaction Types:**
|
||||
|
||||
| Type | Category | Description |
|
||||
|------|----------|-------------|
|
||||
| `STAMP_EARNED` | Stamps | Customer earned a stamp |
|
||||
| `STAMP_REDEEMED` | Stamps | Stamps exchanged for reward |
|
||||
| `STAMP_VOIDED` | Stamps | Stamp reversed (return) |
|
||||
| `STAMP_ADJUSTMENT` | Stamps | Manual adjustment |
|
||||
| `POINTS_EARNED` | Points | Points from purchase |
|
||||
| `POINTS_REDEEMED` | Points | Points exchanged for reward |
|
||||
| `POINTS_VOIDED` | Points | Points reversed (return) |
|
||||
| `POINTS_ADJUSTMENT` | Points | Manual adjustment |
|
||||
| `POINTS_EXPIRED` | Points | Points expired due to inactivity |
|
||||
| `CARD_CREATED` | Lifecycle | Card enrollment |
|
||||
| `CARD_DEACTIVATED` | Lifecycle | Card deactivated |
|
||||
| `WELCOME_BONUS` | Bonus | Welcome bonus points on enrollment |
|
||||
|
||||
### StaffPin
|
||||
|
||||
Staff authentication PINs for fraud prevention. Scoped to a store within a merchant's program.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `merchant_id` | FK | Merchant |
|
||||
| `program_id` | FK | Associated program |
|
||||
| `store_id` | FK | Store this PIN is for |
|
||||
| `name` | String | Staff member name |
|
||||
| `staff_id` | String | External staff identifier |
|
||||
| `pin_hash` | String | Bcrypt-hashed PIN |
|
||||
| `failed_attempts` | Integer | Consecutive failed attempts |
|
||||
| `locked_until` | DateTime | Lockout expiry (nullable) |
|
||||
| `last_used_at` | DateTime | Last successful use |
|
||||
| `is_active` | Boolean | Whether PIN is active |
|
||||
|
||||
### MerchantLoyaltySettings
|
||||
|
||||
Admin-controlled settings for a merchant's loyalty program. Separate from program config to allow admin overrides.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `merchant_id` | FK (unique) | One settings record per merchant |
|
||||
| `staff_pin_policy` | Enum | REQUIRED, OPTIONAL, or DISABLED |
|
||||
| `staff_pin_lockout_attempts` | Integer | Failed attempts before lockout |
|
||||
| `staff_pin_lockout_minutes` | Integer | Lockout duration |
|
||||
| `allow_self_enrollment` | Boolean | Whether customers can self-enroll |
|
||||
| `allow_void_transactions` | Boolean | Whether voids are allowed |
|
||||
| `allow_cross_location_redemption` | Boolean | Cross-store redemption |
|
||||
| `require_order_reference` | Boolean | Require order ref for points |
|
||||
| `log_ip_addresses` | Boolean | Log IPs in transactions |
|
||||
|
||||
### AppleDeviceRegistration
|
||||
|
||||
Tracks Apple devices registered for wallet push notifications when card balances change.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `card_id` | FK | Associated loyalty card |
|
||||
| `device_library_identifier` | String | Apple device identifier |
|
||||
| `push_token` | String | APNs push token |
|
||||
|
||||
Unique constraint on `(device_library_identifier, card_id)`.
|
||||
Reference in New Issue
Block a user