refactor: remove all backward compatibility code across 70 files
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running

Clean up 28 backward compatibility instances identified in the codebase.
The app is not live, so all shims are replaced with the target architecture:

- Remove legacy Inventory.location column (use bin_location exclusively)
- Remove dashboard _extract_metric_value helper (use flat metrics dict)
- Remove legacy stat field duplicates (total_stores, total_imports, etc.)
- Remove 13 re-export shims and class aliases across modules
- Remove module-enabling JSON fallback (use PlatformModule junction table)
- Remove menu_to_legacy_format() conversion (return dataclasses directly)
- Remove title/description from MarketplaceProductBase schema
- Clean billing convenience method docstrings
- Clean test fixtures and backward-compat comments
- Add PlatformModule seeding to init_production.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 13:20:29 +01:00
parent b0db8133a0
commit aad18c27ab
70 changed files with 501 additions and 841 deletions

View File

@@ -173,20 +173,20 @@ Login as a customer (e.g., `customer1@orion.example.com`).
---
## Production URLs (loyalty.lu)
## Production URLs (rewardflow.lu)
In production, the platform uses **domain-based routing** instead of the `/platforms/loyalty/` path prefix.
Store context is detected via **custom domains** (registered in `store_domains` table)
or **subdomains** of `loyalty.lu` (from `Store.subdomain`).
or **subdomains** of `rewardflow.lu` (from `Store.subdomain`).
### URL Routing Summary
| Routing mode | Priority | Pattern | Example |
|-------------|----------|---------|---------|
| Platform domain | — | `loyalty.lu/...` | Admin pages, public API |
| Platform domain | — | `rewardflow.lu/...` | Admin pages, public API |
| Store custom domain | 1 (highest) | `{custom_domain}/...` | Store with its own domain (overrides merchant domain) |
| Merchant domain | 2 | `{merchant_domain}/...` | All stores inherit merchant's domain |
| Store subdomain | 3 (fallback) | `{store_code}.loyalty.lu/...` | Default when no custom/merchant domain |
| Store subdomain | 3 (fallback) | `{store_code}.rewardflow.lu/...` | Default when no custom/merchant domain |
!!! info "Domain Resolution Priority"
When a request arrives, the middleware resolves the store in this order:
@@ -295,58 +295,58 @@ store when the URL includes `/store/{store_code}/...`.
### Case 3: Store without custom domain (uses platform subdomain)
The store has no entry in `store_domains` and the merchant has no registered domain.
**All** store URLs are served via a subdomain of the platform domain: `{store_code}.loyalty.lu`.
**All** store URLs are served via a subdomain of the platform domain: `{store_code}.rewardflow.lu`.
**Storefront (customer-facing):**
| Page | Production URL |
|------|----------------|
| Loyalty Dashboard | `https://bookstore.loyalty.lu/account/loyalty` |
| Transaction History | `https://bookstore.loyalty.lu/account/loyalty/history` |
| Self-Enrollment | `https://bookstore.loyalty.lu/loyalty/join` |
| Enrollment Success | `https://bookstore.loyalty.lu/loyalty/join/success` |
| Loyalty Dashboard | `https://bookstore.rewardflow.lu/account/loyalty` |
| Transaction History | `https://bookstore.rewardflow.lu/account/loyalty/history` |
| Self-Enrollment | `https://bookstore.rewardflow.lu/loyalty/join` |
| Enrollment Success | `https://bookstore.rewardflow.lu/loyalty/join/success` |
**Storefront API:**
| Method | Production URL |
|--------|----------------|
| GET card | `https://bookstore.loyalty.lu/api/storefront/loyalty/card` |
| GET transactions | `https://bookstore.loyalty.lu/api/storefront/loyalty/transactions` |
| POST enroll | `https://bookstore.loyalty.lu/api/storefront/loyalty/enroll` |
| GET program | `https://bookstore.loyalty.lu/api/storefront/loyalty/program` |
| GET card | `https://bookstore.rewardflow.lu/api/storefront/loyalty/card` |
| GET transactions | `https://bookstore.rewardflow.lu/api/storefront/loyalty/transactions` |
| POST enroll | `https://bookstore.rewardflow.lu/api/storefront/loyalty/enroll` |
| GET program | `https://bookstore.rewardflow.lu/api/storefront/loyalty/program` |
**Store backend (staff/owner):**
| Page | Production URL |
|------|----------------|
| Store Login | `https://bookstore.loyalty.lu/store/BOOKSTORE/login` |
| Terminal | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/terminal` |
| Cards | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/cards` |
| Settings | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/settings` |
| Stats | `https://bookstore.loyalty.lu/store/BOOKSTORE/loyalty/stats` |
| Store Login | `https://bookstore.rewardflow.lu/store/BOOKSTORE/login` |
| Terminal | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/terminal` |
| Cards | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/cards` |
| Settings | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/settings` |
| Stats | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/stats` |
**Store API:**
| Method | Production URL |
|--------|----------------|
| GET program | `https://bookstore.loyalty.lu/api/store/loyalty/program` |
| POST stamp | `https://bookstore.loyalty.lu/api/store/loyalty/stamp` |
| POST points | `https://bookstore.loyalty.lu/api/store/loyalty/points` |
| POST enroll | `https://bookstore.loyalty.lu/api/store/loyalty/cards/enroll` |
| POST lookup | `https://bookstore.loyalty.lu/api/store/loyalty/cards/lookup` |
| GET program | `https://bookstore.rewardflow.lu/api/store/loyalty/program` |
| POST stamp | `https://bookstore.rewardflow.lu/api/store/loyalty/stamp` |
| POST points | `https://bookstore.rewardflow.lu/api/store/loyalty/points` |
| POST enroll | `https://bookstore.rewardflow.lu/api/store/loyalty/cards/enroll` |
| POST lookup | `https://bookstore.rewardflow.lu/api/store/loyalty/cards/lookup` |
### Platform Admin & Public API (always on platform domain)
| Page / Endpoint | Production URL |
|-----------------|----------------|
| Admin Programs | `https://loyalty.lu/admin/loyalty/programs` |
| Admin Analytics | `https://loyalty.lu/admin/loyalty/analytics` |
| Admin Merchant Detail | `https://loyalty.lu/admin/loyalty/merchants/{id}` |
| Admin Merchant Settings | `https://loyalty.lu/admin/loyalty/merchants/{id}/settings` |
| Admin API - Programs | `GET https://loyalty.lu/api/admin/loyalty/programs` |
| Admin API - Stats | `GET https://loyalty.lu/api/admin/loyalty/stats` |
| Public API - Program | `GET https://loyalty.lu/api/loyalty/programs/ORION` |
| Apple Wallet Pass | `GET https://loyalty.lu/api/loyalty/passes/apple/{serial}.pkpass` |
| Admin Programs | `https://rewardflow.lu/admin/loyalty/programs` |
| Admin Analytics | `https://rewardflow.lu/admin/loyalty/analytics` |
| Admin Merchant Detail | `https://rewardflow.lu/admin/loyalty/merchants/{id}` |
| Admin Merchant Settings | `https://rewardflow.lu/admin/loyalty/merchants/{id}/settings` |
| Admin API - Programs | `GET https://rewardflow.lu/api/admin/loyalty/programs` |
| Admin API - Stats | `GET https://rewardflow.lu/api/admin/loyalty/stats` |
| Public API - Program | `GET https://rewardflow.lu/api/loyalty/programs/ORION` |
| Apple Wallet Pass | `GET https://rewardflow.lu/api/loyalty/passes/apple/{serial}.pkpass` |
### Domain configuration per store (current DB state)
@@ -364,11 +364,11 @@ The store has no entry in `store_domains` and the merchant has no registered dom
|-------|----------|---------------------|------------------|
| ORION | WizaCorp | `orion.shop` | `orion.shop` (store override) |
| FASHIONHUB | Fashion Group | `fashionhub.store` | `fashionhub.store` (store override) |
| WIZAGADGETS | WizaCorp | _(none)_ | `wizagadgets.loyalty.lu` (subdomain fallback) |
| WIZAHOME | WizaCorp | _(none)_ | `wizahome.loyalty.lu` (subdomain fallback) |
| FASHIONOUTLET | Fashion Group | _(none)_ | `fashionoutlet.loyalty.lu` (subdomain fallback) |
| BOOKSTORE | BookWorld | _(none)_ | `bookstore.loyalty.lu` (subdomain fallback) |
| BOOKDIGITAL | BookWorld | _(none)_ | `bookdigital.loyalty.lu` (subdomain fallback) |
| WIZAGADGETS | WizaCorp | _(none)_ | `wizagadgets.rewardflow.lu` (subdomain fallback) |
| WIZAHOME | WizaCorp | _(none)_ | `wizahome.rewardflow.lu` (subdomain fallback) |
| FASHIONOUTLET | Fashion Group | _(none)_ | `fashionoutlet.rewardflow.lu` (subdomain fallback) |
| BOOKSTORE | BookWorld | _(none)_ | `bookstore.rewardflow.lu` (subdomain fallback) |
| BOOKDIGITAL | BookWorld | _(none)_ | `bookdigital.rewardflow.lu` (subdomain fallback) |
!!! example "After merchant domain registration"
If WizaCorp registers `myloyaltyprogram.lu` as their merchant domain, the table becomes:
@@ -384,7 +384,7 @@ The store has no entry in `store_domains` and the merchant has no registered dom
1. **Store custom domain**: `orion.shop` (from `store_domains` table) — highest priority
2. **Merchant domain**: `myloyaltyprogram.lu` (from `merchant_domains` table) — inherited default
3. **Subdomain fallback**: `orion.loyalty.lu` (from `Store.subdomain` + platform domain)
3. **Subdomain fallback**: `orion.rewardflow.lu` (from `Store.subdomain` + platform domain)
---
@@ -420,7 +420,7 @@ flowchart TD
1. Login as `john.owner@wizacorp.com` and navigate to billing:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/billing`
- Prod (custom domain): `https://orion.shop/store/ORION/billing`
- Prod (subdomain): `https://orion.loyalty.lu/store/ORION/billing`
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/billing`
2. View available subscription tiers:
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/store/billing/tiers`
- API Prod: `GET https://{store_domain}/api/v1/store/billing/tiers`
@@ -441,19 +441,19 @@ flowchart TD
1. Platform admin registers a merchant domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/{merchant_id}/domains`
- API Prod: `POST https://loyalty.lu/api/v1/admin/merchants/{merchant_id}/domains`
- API Prod: `POST https://rewardflow.lu/api/v1/admin/merchants/{merchant_id}/domains`
- Body: `{"domain": "myloyaltyprogram.lu", "is_primary": true}`
2. The API returns a `verification_token` for DNS verification
3. Get DNS verification instructions:
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
- API Prod: `GET https://loyalty.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
- API Prod: `GET https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
4. Merchant adds a DNS TXT record: `_orion-verify.myloyaltyprogram.lu TXT {verification_token}`
5. Verify the domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
- API Prod: `POST https://loyalty.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
- API Prod: `POST https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
6. Activate the domain:
- API Dev: `PUT http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}`
- API Prod: `PUT https://loyalty.lu/api/v1/admin/merchants/domains/merchant/{domain_id}`
- API Prod: `PUT https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}`
- Body: `{"is_active": true}`
7. All merchant stores now inherit `myloyaltyprogram.lu` as their effective domain
@@ -463,7 +463,7 @@ If a store needs its own domain (e.g., ORION is a major brand and wants `mysuper
1. Platform admin registers a store domain:
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/stores/{store_id}/domains`
- API Prod: `POST https://loyalty.lu/api/v1/admin/stores/{store_id}/domains`
- API Prod: `POST https://rewardflow.lu/api/v1/admin/stores/{store_id}/domains`
- Body: `{"domain": "mysuperloyaltyprogram.lu", "is_primary": true}`
2. Follow the same DNS verification and activation flow as merchant domains
3. Once active, this store's effective domain becomes `mysuperloyaltyprogram.lu` (overrides merchant domain)
@@ -509,11 +509,11 @@ flowchart TD
1. Login as `john.owner@wizacorp.com` at:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/login`
- Prod (custom domain): `https://orion.shop/store/ORION/login`
- Prod (subdomain): `https://orion.loyalty.lu/store/ORION/login`
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/login`
2. Navigate to loyalty settings:
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/settings`
- Prod (custom domain): `https://orion.shop/store/ORION/loyalty/settings`
- Prod (subdomain): `https://orion.loyalty.lu/store/ORION/loyalty/settings`
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/loyalty/settings`
3. Create a new loyalty program:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/program`
- Prod: `POST https://{store_domain}/api/store/loyalty/program`
@@ -526,7 +526,7 @@ flowchart TD
- Prod: `POST https://{store_domain}/api/store/loyalty/pins`
9. Verify program is live - check from another store (same merchant):
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/settings`
- Prod (subdomain): `https://wizagadgets.loyalty.lu/store/WIZAGADGETS/loyalty/settings`
- Prod (subdomain): `https://wizagadgets.rewardflow.lu/store/WIZAGADGETS/loyalty/settings`
**Expected blockers in current state:**
@@ -649,19 +649,19 @@ flowchart TD
1. Visit the public enrollment page:
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join`
- Prod (custom domain): `https://orion.shop/loyalty/join`
- Prod (subdomain): `https://bookstore.loyalty.lu/loyalty/join`
- Prod (subdomain): `https://bookstore.rewardflow.lu/loyalty/join`
2. Fill in enrollment form (email, name)
3. Submit enrollment:
- Dev: `POST http://localhost:9999/platforms/loyalty/api/storefront/loyalty/enroll`
- Prod (custom domain): `POST https://orion.shop/api/storefront/loyalty/enroll`
- Prod (subdomain): `POST https://bookstore.loyalty.lu/api/storefront/loyalty/enroll`
- Prod (subdomain): `POST https://bookstore.rewardflow.lu/api/storefront/loyalty/enroll`
4. Redirected to success page:
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (custom domain): `https://orion.shop/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (subdomain): `https://bookstore.loyalty.lu/loyalty/join/success?card=XXXX-XXXX-XXXX`
- Prod (subdomain): `https://bookstore.rewardflow.lu/loyalty/join/success?card=XXXX-XXXX-XXXX`
5. Optionally download Apple Wallet pass:
- Dev: `GET http://localhost:9999/platforms/loyalty/api/loyalty/passes/apple/{serial_number}.pkpass`
- Prod: `GET https://loyalty.lu/api/loyalty/passes/apple/{serial_number}.pkpass`
- Prod: `GET https://rewardflow.lu/api/loyalty/passes/apple/{serial_number}.pkpass`
---
@@ -676,13 +676,13 @@ flowchart TD
2. View loyalty dashboard (card balance, available rewards):
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty`
- Prod (custom domain): `https://orion.shop/account/loyalty`
- Prod (subdomain): `https://bookstore.loyalty.lu/account/loyalty`
- Prod (subdomain): `https://bookstore.rewardflow.lu/account/loyalty`
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/storefront/loyalty/card`
- API Prod: `GET https://orion.shop/api/storefront/loyalty/card`
3. View full transaction history:
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty/history`
- Prod (custom domain): `https://orion.shop/account/loyalty/history`
- Prod (subdomain): `https://bookstore.loyalty.lu/account/loyalty/history`
- Prod (subdomain): `https://bookstore.rewardflow.lu/account/loyalty/history`
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/storefront/loyalty/transactions`
- API Prod: `GET https://orion.shop/api/storefront/loyalty/transactions`
@@ -698,22 +698,22 @@ flowchart TD
1. Login as admin
2. View all programs:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/programs`
- Prod: `https://loyalty.lu/admin/loyalty/programs`
- Prod: `https://rewardflow.lu/admin/loyalty/programs`
3. View platform-wide analytics:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/analytics`
- Prod: `https://loyalty.lu/admin/loyalty/analytics`
- Prod: `https://rewardflow.lu/admin/loyalty/analytics`
4. Drill into WizaCorp's program:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/1`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1`
5. Manage WizaCorp's merchant-level settings:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/1/settings`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
- API Dev: `PATCH http://localhost:9999/platforms/loyalty/api/admin/loyalty/merchants/1/settings`
- API Prod: `PATCH https://loyalty.lu/api/admin/loyalty/merchants/1/settings`
- API Prod: `PATCH https://rewardflow.lu/api/admin/loyalty/merchants/1/settings`
6. Adjust settings: PIN policy, self-enrollment toggle, void permissions
7. Check other merchants:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/2`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/2`
---
@@ -752,7 +752,7 @@ flowchart TD
**Precondition:** Cross-location redemption must be enabled in merchant settings:
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
- Prod: `https://loyalty.lu/admin/loyalty/merchants/1/settings`
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
**Steps:**