TorbatYar/docs/loyalty-phase-7-2.md
Mortezakoohjani 071c484530 Ship Loyalty phases 7.2-7.6 (points through wallet) with production deploy.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 21:27:22 +03:30

102 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 7.2 — Point Engine (Immutable Ledger)
| Field | Value |
| --- | --- |
| Identifier | `loyalty-7.2` |
| Status | Complete |
| Module | loyalty |
| Service | `loyalty-service` |
| Version | `0.7.2.0` |
| Database | `loyalty_db` |
| Depends On | Phase 7.1 |
| ADR(s) | ADR-001, ADR-003, ADR-006, ADR-011 |
| Manifest | [phase-manifest.yaml](ai-framework/phase-manifest.yaml) |
## Objective
Deliver the Point Engine: append-only immutable ledger as the sole source of point balances, with earn / redeem / adjust / expire operations, idempotency, and balance APIs — without rewards redemption, campaigns, wallet, or gift cards.
## Scope
### In Scope
- `PointLedgerEntry` append-only model (signed amounts; `balance_after`)
- Balance = `SUM(amount)` — never stored on `PointAccount`
- Earn, redeem, adjust, expire operations
- Idempotency via optional `idempotency_key` (tenant-unique)
- Insufficient-balance rejection on redeem/expire
- Permissions, events, validators, migration `0003_phase_72_points`
- Tests + documentation + handover + service snapshot
### Out of Scope
- Reward catalog redemption flows (7.3)
- Campaign rule engine (7.4)
- Referral / wallet / gift cards / analytics / partner APIs (7.57.9)
## Ledger Rules
| Rule | Detail |
| --- | --- |
| Append-only | No UPDATE/DELETE of ledger rows |
| Signed amounts | Earn/adjust+ positive; redeem/expire/adjust negative |
| Open accounts only | Frozen/closed accounts reject mutations |
| Idempotency | Same `(tenant_id, idempotency_key)` returns existing entry |
| Balance | Computed from ledger; never a mutable column on `PointAccount` |
## Models
| Entity | Soft delete | Audit | Tenant |
| --- | --- | --- | --- |
| PointLedgerEntry | No (append-only) | Via fields + audit log | Yes |
## APIs
| Method | Path | Permission |
| --- | --- | --- |
| GET | `/api/v1/point-accounts/{id}/balance` | `loyalty.points.view` |
| GET | `/api/v1/point-accounts/{id}/ledger` | `loyalty.points.view` |
| POST | `/api/v1/point-accounts/{id}/earn` | `loyalty.points.earn` |
| POST | `/api/v1/point-accounts/{id}/redeem` | `loyalty.points.redeem` |
| POST | `/api/v1/point-accounts/{id}/adjust` | `loyalty.points.adjust` |
| POST | `/api/v1/point-accounts/{id}/expire-points` | `loyalty.points.expire` |
## Events
| Event | When |
| --- | --- |
| `loyalty.points.earned` | Earn |
| `loyalty.points.redeemed` | Redeem |
| `loyalty.points.adjusted` | Adjust |
| `loyalty.points.expired` | Expire points |
## Migration
| Item | Detail |
| --- | --- |
| Alembic | `0003_phase_72_points` (down_revision `0002_phase_71_membership`) |
| Breaking | None — additive table |
| Backfill | None required |
## Tests
Command: `cd backend/services/loyalty && pytest -q`**61 passed**
## Known Limitations
- No automatic expire scheduler (API-driven expire only)
- No FIFO lot tracking for expiration (bulk expire by amount)
- Transfer still does not migrate ledger entries (explicit follow-up if needed)
## Next Phase
Phase 7.3 — Rewards Engine
## Related Documents
- [Handover](phase-handover/phase-7-2.md)
- [Phase 7.1](loyalty-phase-7-1.md)
- [ADR-011](architecture/adr/ADR-011.md)
- [Service Snapshot](service-snapshots/loyalty.yaml)
- [Module Registry](module-registry.md#loyalty)