TorbatYar/docs/loyalty-phase-7-1.md
Mortezakoohjani e41ecfad4c Sync platform docs, infra, and module services with Accounting integration.
Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 22:35:23 +03:30

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)