fix(loyalty): cross-store enrollment, card scoping, i18n flicker
Some checks failed
Some checks failed
Fix duplicate card creation when the same email enrolls at different stores under the same merchant, and implement cross-location-aware enrollment behavior. - Cross-location enabled (default): one card per customer per merchant. Re-enrolling at another store returns the existing card with a "works at all our locations" message + store list. - Cross-location disabled: one card per customer per store. Enrolling at a different store creates a separate card for that store. Changes: - Migration loyalty_004: replace (merchant_id, customer_id) unique index with (enrolled_at_store_id, customer_id). Per-merchant uniqueness enforced at application layer when cross-location enabled. - card_service.resolve_customer_id: cross-store email lookup via merchant_id param to find existing cardholders at other stores. - card_service.enroll_customer: branch duplicate check on allow_cross_location_redemption setting. - card_service.search_card_for_store: cross-store email search when cross-location enabled so staff at store2 can find cards from store1. - card_service.get_card_by_customer_and_store: new service method. - storefront enrollment: catch LoyaltyCardAlreadyExistsException, return existing card with already_enrolled flag, locations, and cross-location context. Server-rendered i18n via Jinja2 tojson. - enroll-success.html: conditional cross-store/single-store messaging, server-rendered translations and context, i18n_modules block added. - dashboard.html, history.html: replace $t() with server-side _() to fix i18n flicker across all storefront templates. - Fix device-mobile icon → phone icon. - 4 new i18n keys in 4 locales (en, fr, de, lb). - Docs: updated data-model, business-logic, production-launch-plan, user-journeys with cross-location behavior and E2E test checklist. - 12 new unit tests + 3 new integration tests (334 total pass). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -792,3 +792,109 @@ flowchart TD
|
||||
There is no feature gating on loyalty program creation — you can test them in
|
||||
either order. Journey 0 is listed second because domain setup is about URL
|
||||
presentation, not a functional prerequisite for the loyalty module.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Launch E2E Test Checklist (Fashion Group)
|
||||
|
||||
Manual end-to-end checklist using Fashion Group (merchant 2: FASHIONHUB + FASHIONOUTLET).
|
||||
Covers all customer-facing flows including the cross-store enrollment and redemption features
|
||||
added in the Phase 1 production launch hardening.
|
||||
|
||||
### Pre-requisite: Program Setup (Journey 1)
|
||||
|
||||
If Fashion Group doesn't have a loyalty program yet:
|
||||
|
||||
1. Login as `jane.owner@fashiongroup.com` at `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/login`
|
||||
2. Navigate to: `http://localhost:8000/platforms/loyalty/store/FASHIONHUB/loyalty/settings`
|
||||
3. Create program (hybrid or points), set welcome bonus, enable self-enrollment
|
||||
4. Verify Cross-Location Redemption is **enabled** in merchant settings
|
||||
|
||||
### Test 1: Customer Self-Enrollment (Journey 4)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 1.1 | Visit `http://localhost:8000/platforms/loyalty/storefront/FASHIONHUB/loyalty/join` | Enrollment form loads, no console errors |
|
||||
| 1.2 | Fill in: fresh email, name, **birthday** → Submit | Redirected to success page with card number |
|
||||
| 1.3 | Check DB: `SELECT birth_date FROM customers WHERE email = '...'` | `birth_date` is set (not NULL) |
|
||||
| 1.4 | Enroll **without** birthday (different email) | Success, `birth_date` is NULL (no crash) |
|
||||
|
||||
### Test 2: Cross-Store Re-Enrollment (Cross-Location Enabled)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 2.1 | Visit `http://localhost:8000/platforms/loyalty/storefront/FASHIONOUTLET/loyalty/join` | Enrollment form loads |
|
||||
| 2.2 | Submit with the **same email** from Test 1 | Success page shows **"You're already a member!"** |
|
||||
| 2.3 | Check: store list shown | Blue box: "Your card works at all our locations:" with Fashion Hub + Fashion Outlet listed |
|
||||
| 2.4 | Check: same card number as Test 1 | Card number matches (no duplicate created) |
|
||||
| 2.5 | Check DB: `SELECT COUNT(*) FROM loyalty_cards WHERE customer_id = ...` | Exactly 1 card |
|
||||
| 2.6 | Re-enroll at FASHIONHUB (same store as original) | Same behavior: "already a member" + locations |
|
||||
| 2.7 | Refresh the success page | Message persists, no flicker, no untranslated i18n keys |
|
||||
|
||||
### Test 3: Staff Operations — Stamps/Points (Journeys 2 & 3)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 3.1 | Login as `jane.owner@fashiongroup.com` at FASHIONHUB | Login succeeds |
|
||||
| 3.2 | Open terminal: `.../store/FASHIONHUB/loyalty/terminal` | Terminal loads |
|
||||
| 3.3 | Look up card by card number | Card found, balance displayed |
|
||||
| 3.4 | Look up card by customer email | Card found (same result) |
|
||||
| 3.5 | Add stamp (or earn points with purchase amount) | Count/balance updates |
|
||||
| 3.6 | Add stamp again immediately (within cooldown) | Rejected: cooldown active |
|
||||
|
||||
### Test 4: Cross-Store Redemption (Journey 8)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 4.1 | Staff at FASHIONHUB adds stamps/points to the card | Balance updated |
|
||||
| 4.2 | Login as staff at FASHIONOUTLET (e.g., `diana.stylist@fashiongroup.com` or `jane.owner`) | Login succeeds |
|
||||
| 4.3 | Open terminal: `.../store/FASHIONOUTLET/loyalty/terminal` | Terminal loads |
|
||||
| 4.4 | Look up card **by email** | Card found (cross-store email search) |
|
||||
| 4.5 | Look up card **by card number** | Card found |
|
||||
| 4.6 | Redeem reward (if enough stamps/points) | Redemption succeeds |
|
||||
| 4.7 | View card detail | Transaction history shows entries from both FASHIONHUB and FASHIONOUTLET |
|
||||
|
||||
### Test 5: Customer Views Status (Journey 5)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 5.1 | Login as the customer at storefront | Customer dashboard loads |
|
||||
| 5.2 | Dashboard: `.../storefront/FASHIONHUB/account/loyalty` | Shows balance, available rewards |
|
||||
| 5.3 | History: `.../storefront/FASHIONHUB/account/loyalty/history` | Shows transactions from both stores |
|
||||
|
||||
### Test 6: Void/Return (Journey 7)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 6.1 | Staff at FASHIONHUB opens terminal, looks up card | Card found |
|
||||
| 6.2 | Void a stamp or points transaction | Balance adjusted |
|
||||
| 6.3 | Check transaction history | Void transaction appears, linked to original |
|
||||
|
||||
### Test 7: Admin Oversight (Journey 6)
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 7.1 | Login as `samir.boulahtit@gmail.com` (admin) | Admin dashboard loads |
|
||||
| 7.2 | Programs: `.../admin/loyalty/programs` | Fashion Group program visible |
|
||||
| 7.3 | Fashion Group detail: `.../admin/loyalty/merchants/2` | Cards, transactions, stats appear correctly |
|
||||
| 7.4 | Fashion Group settings: `.../admin/loyalty/merchants/2/settings` | Cross-location toggle visible and correct |
|
||||
|
||||
### Test 8: Cross-Location Disabled Behavior
|
||||
|
||||
| Step | Action | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| 8.1 | Admin disables Cross-Location Redemption for Fashion Group | Setting saved |
|
||||
| 8.2 | Enroll a **new email** at FASHIONHUB | New card created for FASHIONHUB |
|
||||
| 8.3 | Enroll **same email** at FASHIONOUTLET | **New card created** for FASHIONOUTLET (separate card) |
|
||||
| 8.4 | Enroll **same email** at FASHIONHUB again | "Already a member" — shows "Your card is registered at Fashion Hub" (single store, no list) |
|
||||
| 8.5 | Staff at FASHIONOUTLET searches by email | Only finds the FASHIONOUTLET card (no cross-store search) |
|
||||
| 8.6 | Re-enable Cross-Location Redemption when done | Restore default state |
|
||||
|
||||
### Key Things to Watch
|
||||
|
||||
- [ ] Birthday persisted after enrollment (check DB)
|
||||
- [ ] No i18n flicker or console warnings on success page
|
||||
- [ ] Cross-store email search works in the terminal (cross-location enabled)
|
||||
- [ ] "Already a member" message shows correct locations/store based on cross-location setting
|
||||
- [ ] No duplicate cards created under same merchant (when cross-location enabled)
|
||||
- [ ] Rate limiting: rapid-fire stamp calls eventually return 429
|
||||
|
||||
Reference in New Issue
Block a user