Files
nick-doc/02 - Data Models/PointTransaction.md
Siavash Sameni 7a616744f4 docs: complete code-reality alignment for remaining docs + reconcile issue set
Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
  Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
  added undocumented endpoints (ton-proof challenge, profile email verify,
  GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
  enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
  90-day notification TTL, soft-delete semantics, wallet fields

Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
  page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
  TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation

Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
  ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 15:15:02 +04:00

5.1 KiB

title, tags, aliases
title tags aliases
PointTransaction
data-model
mongoose
Point Ledger
Loyalty Transaction
IPointTransaction

Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)

PointTransaction

Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)

Append-only ledger of loyalty point movements. Each row represents one earn / spend / expire event for a user, with a source attribution (purchase / referral / bonus / admin / redemption), the amount moved, and the resulting balance snapshot. Metadata is flexible to support different sources (order amount, commission, level changes, referenced PurchaseRequest).

[!warning] type enum is earn / spend / expire ONLY There is no refund type (nor any other value). The enum at PointTransaction.ts:35 is exactly ['earn', 'spend', 'expire']. Referral earns are identified by source: 'referral' + type: 'earn', not by a dedicated type.

[!danger] expire is defined but never produced The expiresAt field and the 'expire' type exist in the schema, and there is a sparse { expiresAt: 1 } index intended for expiry sweeps — but no service, cron job, or TTL ever creates an expire-type transaction. Point expiry is not enforced anywhere in the codebase today; points effectively never expire.

[!note] Source backend/src/models/PointTransaction.ts:25 — schema definition backend/src/models/PointTransaction.ts:84 — model export

Schema

Field Type Required Default Validation Index Description
user ObjectId → User yes yes (single + compound) Owner of the transaction.
type String yes enum: earn / spend / expire yes (compound) Movement direction.
source String yes enum: purchase / referral / bonus / admin / redemption yes (compound) Source bucket. Referral earns are identified by source='referral' (with type='earn'), not by type. Redemptions use source='redemption'; admin grants use source='admin'.
amount Number yes Points moved (positive integer; semantics by type).
balance Number yes Available balance after the move.
order ObjectId → Order no Linked order id (legacy ref, see warning).
referredUser ObjectId → User no Referred user (for referral earns).
description String yes Human label.
metadata.orderAmount Number no Order amount snapshot.
metadata.commission Number no Commission snapshot.
metadata.levelBefore Number no Pre-level snapshot.
metadata.levelAfter Number no Post-level snapshot.
metadata.purchaseRequestId String no Linked PurchaseRequest id.
expiresAt Date no yes (sparse) When the points expire (for earn).
createdAt Date auto yes (compound, desc) Mongoose timestamp.
updatedAt Date auto Mongoose timestamp.

[!warning] order reference The schema declares ref: 'Order', but there is no Order model in backend/src/models/. In practice this slot is used for the PurchaseRequest id; consumers should not rely on Mongoose populate('order') working.

Virtuals

None defined.

Indexes

Defined at backend/src/models/PointTransaction.ts:80-82. Plus the implicit index from user being declared with index: true:

  • { user: 1, createdAt: -1 } — user ledger view.
  • { type: 1, source: 1 } — analytics.
  • { expiresAt: 1 } (sparse) — expiry sweeps.

Pre/Post Hooks

None declared.

Instance Methods

None defined.

Static Methods

None defined.

Relationships

  • References: User (user, referredUser).
  • Referenced by: none. Loosely related to PurchaseRequest via metadata.purchaseRequestId (string).

State Transitions

No status field — entries are immutable once written. The schema anticipates a consumer scanning for expiresAt < now to create offsetting type: 'expire' rows, but no such consumer exists: nothing in the codebase ever writes an expire row, so in practice only earn and spend entries are ever created.

Common Queries

// User ledger
PointTransaction.find({ user: userId }).sort({ createdAt: -1 }).limit(50);

// Latest balance (most recent row)
PointTransaction.findOne({ user: userId }).sort({ createdAt: -1 });

// Referral earnings
PointTransaction.find({ user: userId, source: 'referral', type: 'earn' });

// Points expiring soon
PointTransaction.find({ expiresAt: { $lte: oneWeekFromNow }, type: 'earn' });

// Analytics: total earned vs spent per source
PointTransaction.aggregate([
  { $group: { _id: { type: '$type', source: '$source' }, total: { $sum: '$amount' } } }
]);

Related: User, LevelConfig, PurchaseRequest.