Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
4.1 KiB
Markdown
119 lines
4.1 KiB
Markdown
# Phase 7.1 — Membership Engine
|
|
|
|
| Field | Value |
|
|
| --- | --- |
|
|
| Identifier | `loyalty-7.1` |
|
|
| Status | Complete |
|
|
| Module | loyalty |
|
|
| Service | `loyalty-service` |
|
|
| Version | `0.7.1.0` |
|
|
| Database | `loyalty_db` |
|
|
| Depends On | Phase 7.0 |
|
|
| ADR(s) | ADR-001, ADR-003, ADR-006, ADR-011 |
|
|
| Manifest | [phase-manifest.yaml](ai-framework/phase-manifest.yaml) |
|
|
|
|
## Objective
|
|
|
|
Deliver the Membership Engine: lifecycle state machine, eligibility transitions, append-only lifecycle history, program cascade policy, and lifecycle APIs/events — without points ledger, rewards redemption, or campaign engines.
|
|
|
|
## Scope
|
|
|
|
### In Scope
|
|
|
|
- Member lifecycle: activate, renew, freeze, resume, cancel, expire, transfer
|
|
- Enroll path hardened with term / expiry and lifecycle events
|
|
- `MembershipLifecycleEvent` append-only history
|
|
- Cascade policy: reject program soft-delete while blocking members exist
|
|
- Permissions, events, validators, migration `0002_phase_71_membership`
|
|
- Tests + documentation + handover
|
|
|
|
### Out of Scope
|
|
|
|
- Point ledger / balances (7.2)
|
|
- Rewards redemption (7.3)
|
|
- Campaign engine (7.4+)
|
|
- Wallet / gift cards / partner network
|
|
|
|
## Cascade Policy
|
|
|
|
Soft-deleting a `LoyaltyProgram` is **rejected** (`409 program_has_active_members`) when any non-deleted member exists in statuses: `pending`, `active`, `frozen`, `suspended`, `expired`. Cancelled / transferred / closed members do not block.
|
|
|
|
## State Machine
|
|
|
|
| Action | Allowed from | To |
|
|
| --- | --- | --- |
|
|
| enroll / activate | pending (activate also expired) | active |
|
|
| renew | active, expired, frozen, suspended | active |
|
|
| freeze | active | frozen |
|
|
| resume | frozen, suspended | active |
|
|
| cancel | pending, active, frozen, suspended, expired | cancelled |
|
|
| expire | active, frozen, suspended | expired |
|
|
| transfer | active, frozen, suspended | transferred (+ new active member in target program) |
|
|
|
|
Terminal: `cancelled`, `transferred`, `closed`.
|
|
|
|
## Models
|
|
|
|
| Entity | Soft delete | Audit | Tenant |
|
|
| --- | --- | --- | --- |
|
|
| Member (extended fields) | Yes | Yes | Yes |
|
|
| MembershipLifecycleEvent | No (append-only) | Via fields | Yes |
|
|
|
|
New member fields: `activated_at`, `membership_started_at`, `membership_expires_at`, `frozen_at`, `freeze_reason`, `cancelled_at`, `cancel_reason`, `expired_at`, `transferred_to_member_id`.
|
|
|
|
## APIs
|
|
|
|
| Method | Path | Permission |
|
|
| --- | --- | --- |
|
|
| POST | `/api/v1/members/{id}/activate` | `loyalty.members.activate` |
|
|
| POST | `/api/v1/members/{id}/renew` | `loyalty.members.renew` |
|
|
| POST | `/api/v1/members/{id}/freeze` | `loyalty.members.freeze` |
|
|
| POST | `/api/v1/members/{id}/resume` | `loyalty.members.resume` |
|
|
| POST | `/api/v1/members/{id}/cancel` | `loyalty.members.cancel` |
|
|
| POST | `/api/v1/members/{id}/expire` | `loyalty.members.expire` |
|
|
| POST | `/api/v1/members/{id}/transfer` | `loyalty.members.transfer` |
|
|
| GET | `/api/v1/members/{id}/lifecycle` | `loyalty.members.lifecycle.view` |
|
|
|
|
Existing create / enroll / update / delete remain compatible (additive fields).
|
|
|
|
## Events
|
|
|
|
| Event | When |
|
|
| --- | --- |
|
|
| `loyalty.member.activated` | Activate / enroll activation |
|
|
| `loyalty.member.renewed` | Renew |
|
|
| `loyalty.member.frozen` | Freeze |
|
|
| `loyalty.member.resumed` | Resume |
|
|
| `loyalty.member.cancelled` | Cancel |
|
|
| `loyalty.member.expired` | Expire |
|
|
| `loyalty.member.transferred` | Transfer (source) |
|
|
|
|
## Migration
|
|
|
|
| Item | Detail |
|
|
| --- | --- |
|
|
| Alembic | `0002_phase_71_membership` (down_revision `0001_initial`) |
|
|
| Breaking | None — additive columns + new table |
|
|
| Backfill | None required |
|
|
|
|
## Tests
|
|
|
|
Command: `cd backend/services/loyalty && pytest -q` → **59 passed**
|
|
|
|
## Known Limitations
|
|
|
|
- No automatic expire scheduler (API-driven expire only)
|
|
- Transfer copies profile/contact; does not move point accounts (7.2)
|
|
- JWT↔tenant binding remains platform-wide
|
|
|
|
## Next Phase
|
|
|
|
Phase 7.2 — Point Engine (immutable ledger)
|
|
|
|
## Related Documents
|
|
|
|
- [Handover](phase-handover/phase-7-1.md)
|
|
- [Phase 7.0](loyalty-phase-7-0.md)
|
|
- [ADR-011](architecture/adr/ADR-011.md)
|
|
- [Module Registry](module-registry.md#loyalty)
|